Juniper

README.md
commit 9623e4d32694118e68ce8706f29e2cfbc6c5b6dc

Juniper 是 Rust 语言的 GraphQL 服务器库,用最少量的样板文件和配置构建类型安全且快速的 API 服务器。

GraphQL 是Facebook开发的一种数据查询语言,旨在为移动和 Web 应用程序前端提供服务。

Juniper 使得以 Rust 语言编写类型安全且速度惊人的 GraphQL 服务器成为可能,我们还尝试尽可能方便地声明和解析 GraphQL 模式。

Juniper 不包含 Web 服务器,仅提供了构建快,使得其与已有服务器的集成简单明了。Juniper 可选地为 HyperIronRocket,以及 Warp等框架提供了预构建集成,并嵌入了 Graphiql,以便于调试。

译者注: 对于 Juniper 团队没有提供预集成的 Web 框架,如 actix-web,其构建集成也很简单,actix-web 用户提供了完整集成实例。

特点

Juniper 根据 GraphQL 规范定义支持完整的 GraphQL 查询语言,包括:接口、联合、模式内省,以及验证。但是不支持模式语言。

Juniper 作为 Rust 语言的 GraphQL 库,默认构建非空类型。类型为 Vec<Episode> 的字段将被转换为 [Episode!]!,相应的 Rust 语言类型则为 Option<Vec<Option<Episode>>>

集成

数据类型

Juniper 与一些较常见的 Rust 库进行了自动集成,使构建模式变得简单,被集成的 Rust 库中的类型将在 GraphQL 模式中自动可用。

Web 框架

API 稳定性

Juniper 还未发布 1.0 版本,部分 API 稳定性可能不够成熟。

快速入门

quickstart.md
commit 29025e6cae4a249fa56017dcf16b95ee4e89363e

简要介绍 Juniper 中的概念。

安装

!文件名 Cargo.toml

[dependencies]
juniper = "^0.13.1"

模式示例

要将 Rust 语言的 enumsstructs 暴露为 GraphQL,仅需向其增加一个自定义派生属性。Juniper 支持将 Rust 语言基本类型轻而易举地映射到 GraphQL 特性,诸如:Option<T>Vec<T>Box<T>Stringf64i32引用切片(slice).

对于更高级的映射,Juniper 提供了多种宏(macro)来将 Rust 类型映射到 GraphQL 模式。过程宏对象是最重要的宏对象之一,其用于声明解析器对象,你将使用解析器对象来 查询(Query)变更(Mutation) 根(roots)。

use juniper::{FieldResult};

# struct DatabasePool;
# impl DatabasePool {
#     fn get_connection(&self) -> FieldResult<DatabasePool> { Ok(DatabasePool) }
#     fn find_human(&self, _id: &str) -> FieldResult<Human> { Err("")? }
#     fn insert_human(&self, _human: &NewHuman) -> FieldResult<Human> { Err("")? }
# }

#[derive(juniper::GraphQLEnum)]
enum Episode {
    NewHope,
    Empire,
    Jedi,
}

#[derive(juniper::GraphQLObject)]
#[graphql(description="星球大战中的类人生物")]
struct Human {
    id: String,
    name: String,
    appears_in: Vec<Episode>,
    home_planet: String,
}

// 另一个用于映射 GraphQL 输入对象的自定义派生。

#[derive(juniper::GraphQLInputObject)]
#[graphql(description="星球大战中的类人生物")]
struct NewHuman {
    name: String,
    appears_in: Vec<Episode>,
    home_planet: String,
}

// 使用宏对象创建带有解析器的根查询和根变更。
// 对象可以拥有类似数据库池一样的允许访问共享状态的上下文。

struct Context {
    // 这里使用真实数据池
    pool: DatabasePool,
}

// 要让 Juniper 使用上下文,必须实现标注特性(trait)
impl juniper::Context for Context {}

struct Query;

#[juniper::object(
    // 指定对象的上下文类型。
    // 需要访问上下文的每种类型都需如此。
    Context = Context,
)]
impl Query {

    fn apiVersion() -> &str {
        "1.0"
    }

    // 解析器的参数可以是简单类型,也可以是输入对象。
    // 为了访问上下文,我们指定了一个引用上下文类型的参数。
    // Juniper 会自动注入正确的上下文。
    fn human(context: &Context, id: String) -> FieldResult<Human> {
        // 获取数据库连接
        let connection = context.pool.get_connection()?;
        // 执行查询
        // 注意 `?` 的用法,进行错误传播。
        // 译者注:上一章“特点”中提到,Juniper 默认构建非空类型
        let human = connection.find_human(&id)?;
        // 返回结果集
        Ok(human)
    }
}

// 下面对变更类型做同样的事情

struct Mutation;

#[juniper::object(
    Context = Context,
)]
impl Mutation {

    fn createHuman(context: &Context, new_human: NewHuman) -> FieldResult<Human> {
        let db = executor.context().pool.get_connection()?;
        let human: Human = db.insert_human(&new_human)?;
        Ok(human)
    }
}

// 根模式由查询和变更组成,故查询请求可以执行于 RootNode。
type Schema = juniper::RootNode<'static, Query, Mutation>;

# fn main() {
#   let _ = Schema::new(Query, Mutation{});
# }

现在,我们有了一个非常简单,但模式功能齐全的 GraphQL服务器。

要让此模式在服务器端起作用,查阅各类服务器集成指南。

也可以直接调用执行器(executor)来获取查询结果集:

执行器(executor)

可以直接调用 juniper::execute 来运行 GraphQL 查询:

# // 由于宏(macro)不可访问,如下代码仅 Rust-2018 版需要
# #[macro_use] extern crate juniper;
use juniper::{FieldResult, Variables, EmptyMutation};


#[derive(juniper::GraphQLEnum, Clone, Copy)]
enum Episode {
    NewHope,
    Empire,
    Jedi,
}

// 上下文(context)数据
struct Ctx(Episode);

impl juniper::Context for Ctx {}

struct Query;

#[juniper::object(
    Context = Ctx,
)]
impl Query {
    fn favoriteEpisode(context: &Ctx) -> FieldResult<Episode> {
        Ok(context.0)
    }
}


// 根模式由查询和变更组成,故查询请求可以执行于 RootNode。
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>>;

fn main() {
    // 创建上下文对象
    let ctx = Ctx(Episode::NewHope);

    // 运行执行器
    let (res, _errors) = juniper::execute(
        "query { favoriteEpisode }",
        None,
        &Schema::new(Query, EmptyMutation::new()),
        &Variables::new(),
        &ctx,
    ).unwrap();

    // 确保查询结果值匹配
    assert_eq!(
        res,
        graphql_value!({
            "favoriteEpisode": "NEW_HOPE",
        })
    );
}

类型系统

types/index.md
commit a75396846d9f8930d1e07e972a91ff59308e77cf

使用 Juniper 的大部分工作包括将 GraphQL 类型系统映射到应用程序使用的 Rust 语言类型。

Juniper提供了一些恰当方便的抽象,力图使这个过程尽可能地简单。

下面的章节可以查阅更多信息。

定义对象

types/objects/defining_objects.md
commit 693405afa5a86df3a2277065696e7c42306ff630

尽管 Rust 中的任何类型都可以暴露为 GraphQL 对象,但最常见的类型是结构体(struct)。

Juniper 中,有两种方式创建 GraphQL对象:如果想要暴露一个简单结构体(struct),最简单的方式是自定义派生属性;另外一种方式将在复杂字段章节中介绍。

#[derive(juniper::GraphQLObject)]
struct Person {
    name: String,
    age: i32,
}

# fn main() {}

上述代码将创建一个名为 Person 的 GraphQL 对象,有两个字段:String! 类型的 nameInt! 类型的 age。Rust 语言类型系统中,变量绑定默认为非空(non-null)。若你需要可空(nullable)字段,可以使用 Option<T>

我们应当利用 GraphQL 是自文档化(self-documenting)的特点,向类型和字段添加描述。Juniper 将自动使用关联的 Rust 文档注释作为 GraphQL 描述:

!文件名 通过 Rust 文档注释作为 GraphQL 描述

#[derive(juniper::GraphQLObject)]
/// 个人信息
struct Person {
    /// 个人全名,包括姓氏和名字
    name: String,
    /// 个人年龄,以年为单位,按月份四舍五入
    age: i32,
}

# fn main() {}

Rust 中不能使用文档注释的对象和字段,可通过 graphql 属性设置描述。如下示例和上述代码等价:

!文件名 通过 graphql 属性设置描述

#[derive(juniper::GraphQLObject)]
#[graphql(description="个人信息")]
struct Person {
    #[graphql(description="个人全名,包括姓氏和名字")]
    name: String,
    #[graphql(description="个人年龄,以年为单位,按月份四舍五入")]
    age: i32,
}

# fn main() {}

通过 graphql 属性设置的描述优先于 Rust 文档注释,这使得内部 Rust 文档和外部 GraphQL 文档能够不同:

#[derive(juniper::GraphQLObject)]
#[graphql(description="这段描述展示在 GraphQL")]
/// 这段描述展示在 RustDoc
struct Person {
    #[graphql(description="这段描述展示在 GraphQL")]
    /// 这段描述展示在 RustDoc
    name: String,
    /// 这段描述在 RustDoc 和 GraphQL 中都展示
    age: i32,
}

# fn main() {}

关系

如下情形,只能使用自定义派生属性:

  • 注解类型是结构体(struct),
  • 结构体的字段符合以下情形——
    • 简单类型(i32, f64, bool, String, juniper::ID);或者
    • 有效的自定义 GraphQL 类型,如使用此属性标记了其他结构体字段;或者
    • 容器/引用包含以上情形之一,如 Vec<T>Box<T>Option<T>

让我们看看这对于对象之间的构建关系意味着什么:

#[derive(juniper::GraphQLObject)]
struct Person {
    name: String,
    age: i32,
}

#[derive(juniper::GraphQLObject)]
struct House {
    address: Option<String>, // 转换为字符串(可空)
    inhabitants: Vec<Person>, // 转换为 [Person!]!
}

# fn main() {}

因为 Person 是一个有效的 GraphQL 类型,所以可以在另一个结构体中使用 Vec<Person>,它将自动转换为 非空 Person 对象 的列表。

字段重命名

默认地,结构体字段由 Rust 标准命名约定蛇形命名法(snake_case)被转换为 GraphQL 约定的驼峰命名法(snake_case)

#[derive(juniper::GraphQLObject)]
struct Person {
    first_name: String, // GraphQL 模式中将被暴露为 firstName
    last_name: String, // GraphQL 模式中将被暴露为 lastName
}

# fn main() {}

可以在某个结构体字段上使用 graphql 属性指定名称:

#[derive(juniper::GraphQLObject)]
struct Person {
    name: String,
    age: i32,
    #[graphql(name="websiteURL")]
    website_url: Option<String>, // GraphQL 模式中将被暴露为 websiteURL
}

# fn main() {}

字段弃用

要弃用字段,可使用 graphql 属性指定弃用原因:

#[derive(juniper::GraphQLObject)]
struct Person {
    name: String,
    age: i32,
    #[graphql(deprecated = "请使用 name 字段代替")]
    first_name: String,
}

# fn main() {}

当然,名称(name)描述(description)deprecation(弃用)参数可以组合使用。不过 GraphQL 规范中的一些限制依然存在,deprecation(弃用)参数只能用于对象字段和枚举值。

字段忽略

默认地,GraphQLObject 中的所有字段都包含在生成的 GraphQL 类型中。若要不包含特定字段,请使用注解 #[graphql(skip)]

#[derive(juniper::GraphQLObject)]
struct Person {
    name: String,
    age: i32,
    #[graphql(skip)]
    # #[allow(dead_code)]
    password_hash: String, // 此字段不能从 GraphQL 查询或修改
}

# fn main() {}

复杂字段

types/objects/complex_fields.md
commit cff6036206da12f9a4cbddb869569e9a977fa2ef

如果你有一个不能直接映射到 GraphQL 的结构体(struct),其中包含计算字段或循环结构,那么你必须使用一个更强大的工具:过程宏对象。过程宏允许你在 Rust impl 块中为类型定义 GraphQL 对象字段。让我们继续上一章的示例,学习如何使用宏定义 Person


struct Person {
    name: String,
    age: i32,
}

#[juniper::object]
impl Person {
    fn name(&self) -> &str {
        self.name.as_str()
    }

    fn age(&self) -> i32 {
        self.age
    }
}

# fn main() { }

虽然上述示例代码有点冗长,但它允许你在字段解析器中编写任何类型的函数。同时,使用上述示例语法,字段可以接受参数:

#[derive(juniper::GraphQLObject)]
struct Person {
    name: String,
    age: i32,
}

struct House {
    inhabitants: Vec<Person>,
}

#[juniper::object]
impl House {
    // 创建字段 inhabitantWithName(name), 返回非空 person
    fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
        self.inhabitants.iter().find(|p| p.name == name)
    }
}

# fn main() {}

要访问诸如数据库连接或身份验证信息之类的全局数据,需要使用 上下文(context)。更多关于 上下文(context) 的信息,将在下一章介绍:上下文

描述、重命名,以及弃用

与派生属性一样,字段名将从命名约定 snake_case 转换为 camelCase。若需重写转换,可以简单地重命名字段。此外,可以使用别名更换类型名称:


struct Person {
}

/// Rust 文档注释用作 GraphQL 描述。
#[juniper::object(
    // 使用 name 属性,可以更改 GraphQL 类型的公开名称。
    name = "PersonObject",
    // 可以在此处指定 GraphQL 描述,这将覆盖 Rust 文档注释。
    description = "...",
)]
impl Person {

    /// 字段上的文档注释被用作 GraphQL 描述
    #[graphql(
        // 或者指定 GraphQL 描述
        description = "...",
    )]
    fn doc_comment(&self) -> &str {
        ""
    }

    // 如果需要,字段也可以使用 name 属性来重命名
    #[graphql(
        name = "myCustomFieldName",
    )]
    fn renamed_field() -> bool {
        true
    }

    // 如期望的那样,弃用也有效。
    // 即可以接受标准的 Rust 语法,也可以接受自定义属性。
    #[deprecated(note = "...")]
    fn deprecated_standard() -> bool {
        false
    }

    #[graphql(deprecated = "...")]
    fn deprecated_graphql() -> bool {
        true
    }
}

# fn main() { }

自定义参数

方法的字段参数也是可以定制的,可以指定自定义描述和默认值。

注意:此语法目前有点别扭。一旦实现了 Rust RFC 2565,将会变得好用。


struct Person {}

#[juniper::object]
impl Person {
    #[graphql(
        arguments(
            arg1(
                // 设置默认值,如果字段没有被赋值,默认值将被使用。
                // 默认值可以是任何有效的 Rust 表达式,甚至调用函数等。
                default = true,
                // 设定 GraphQL 描述
                description = "第一个参数..."
            ),
            arg2(
                default = 0,
            )
        )
    )]
    fn field1(&self, arg1: bool, arg2: i32) -> String {
        format!("{} {}", arg1, arg2)
    }
}

# fn main() { }

更多功能

GraphQL 字段提供了比 Rust 标准方法提供了更多的功能:

  • 字段的描述和弃用消息;
  • 参数默认值;
  • 参数描述

更多的特性在参考文档中有详细的描述。

上下文

types/objects/using_contexts.md
commit 29025e6cae4a249fa56017dcf16b95ee4e89363e

上下文类型是 Juniper 中的一个特性,它允许字段解析器访问全局数据,最常见的是数据库连接或身份验证信息。上下文通常由 上下文工厂(context factory) 方法创建。上下文的定义,与你正在使用的框架如何集成有关,请查阅 IronRocket 等框架的集成文档。

本章中,将向你展示如何定义上下文类型,以及如何在字段解析器中使用它。假定有一个简单的用户资料库封装在 HashMap 中:

# #![allow(dead_code)]
# use std::collections::HashMap;

struct Database {
    users: HashMap<i32, User>,
}

struct User {
    id: i32,
    name: String,
    friend_ids: Vec<i32>,
}

# fn main() { }

我们希望 User 上的 friends 字段返回 User 对象列表。为了编写这段代码,必须查询数据库。

为了解决这个问题,我们标记 Database 为一个有效的上下文类型,并将其指派给 user 对象。

为了访问上下文,我们需要为被访问的上下文类型指定一个参数,此参数和被访问的 上下文(Context) 类型一致:

# use std::collections::HashMap;
extern crate juniper;

// 此结构体即为将要被访问的上下文
struct Database {
    users: HashMap<i32, User>,
}

// 标记 Database 为一个有效的 Juniper 上下文类型
impl juniper::Context for Database {}

struct User {
    id: i32,
    name: String,
    friend_ids: Vec<i32>,
}


// 指派 Database 作为 User 的上下文类型
#[juniper::object(
    Context = Database,
)]
impl User {
    // 3. 通过给上下文类型指定参数来注入上下文
    // 注意:
    //   - 类型必须是一个 Rust 引用
    //   - 参数名必须是 context
    fn friends(&self, context: &Database) -> Vec<&User> {

        // 5. 使用 database 查找 users
        self.friend_ids.iter()
            .map(|id| context.users.get(id).expect("无法找到匹配该 ID 的用户"))
            .collect()
    }

    fn name(&self) -> &str { 
        self.name.as_str() 
    }

    fn id(&self) -> i32 { 
        self.id 
    }
}

# fn main() { }

你仅获得对上下文的不可变引用,因此,如果你想要执行更改操作,你将需要利用内部可变性(interior mutability),例如:RwLockRefCell

错误处理

types/objects/error_handling.md
commit 29025e6cae4a249fa56017dcf16b95ee4e89363e

Rust 将错误组合成两个主要类别Result<T, E> 处理可恢复错误panic! 处理不可恢复错误。Juniper 对不可恢复错误不做处理;不可恢复错误将上溯到集成 Juniper的框架,然后错误在框架层次有希望得到处理。

对于可恢复错误,Juniper 能够完善地地处理内建的 Result 类型,你可以使用 ? 操作符或者 try! 宏(macro)来让程序按照预期设定工作:

# extern crate juniper;
use std::{
    str,
    path::PathBuf,
    fs::{File},
    io::{Read},
};
use juniper::FieldResult;

struct Example {
    filename: PathBuf,
}

#[juniper::object]
impl Example {
    fn contents() -> FieldResult<String> {
        let mut file = File::open(&self.filename)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        Ok(contents)
    }

    fn foo() -> FieldResult<Option<String>> {
      // 随意的无效字节值
      let invalid = vec![128, 223];

      match str::from_utf8(&invalid) {
        Ok(s) => Ok(Some(s.to_string())),
        Err(e) => Err(e)?,
      }
    }
}

# fn main() {}

FieldResult<T>Result<T, FieldError> 的别名,其是所有字段都必须返回的错误类型。通过使用 ? 操作符或者 try! 宏(macro),任何实现了 Display 特性的类型——这些既是当前绝大多数错误类型——这些错误会自动转换为 FieldError

当字段返回错误时,字段的结果将被 null 替换,然后在响应的顶层附加一个名为 errors 的对象,最后继续执行程序。例如,基于前述的示例代码,在 GraphQL 做如下查询:

{
  example {
    contents
    foo
  }
}

若果 str::from_utf8 导致了 std::str::Utf8Error 错误, 将返回以下内容:

!文件名 错误的可空字段的响应

{
  "data": {
    "example": {
      contents: "<Contents of the file>",
      foo: null,
    }
  },
  "errors": [
    "message": "invalid utf-8 sequence of 2 bytes from index 0",
    "locations": [{ "line": 2, "column": 4 }])
  ]
}

如果非空字段返回错误,如同上述示例代码,null 值将传播到第一个可空的父字段;如果没有可空字段,则传播到根(root)内名为 data 的对象。

举例,执行如下查询:

{
  example {
    contents
  }
}

若果上述代码中的 File::open() 导致 std::io::ErrorKind::PermissionDenied 错误,将返回以下内容:

!文件名 没有可空父子段的非空字段的响应

{
  "errors": [
    "message": "Permission denied (os error 13)",
    "locations": [{ "line": 2, "column": 4 }])
  ]
}

结构化错误

有些情况下,有必要向客户端返回附加的结构化错误信息。可以通过实现 IntoFieldError 来解决:

# #[macro_use] extern crate juniper;
enum CustomError {
    WhateverNotSet,
}

impl juniper::IntoFieldError for CustomError {
    fn into_field_error(self) -> juniper::FieldError {
        match self {
            CustomError::WhateverNotSet => juniper::FieldError::new(
                "不存在任何东东",
                graphql_value!({
                    "type": "NO_WHATEVER"
                }),
            ),
        }
    }
}

struct Example {
    whatever: Option<bool>,
}

#[juniper::object]
impl Example {
    fn whatever() -> Result<bool, CustomError> {
      if let Some(value) = self.whatever {
        return Ok(value);
      }
      Err(CustomError::WhateverNotSet)
    }
}

# fn main() {}

指定的结构化错误信息被包含在名为 extensions 的键值中:

{
  "errors": [
    "message": "不存在任何东东",
    "locations": [{ "line": 2, "column": 4 }]),
    "extensions": {
      "type": "NO_WHATEVER"
    }
  ]
}

其他类型

types/other-index.md
commit a75396846d9f8930d1e07e972a91ff59308e77cf

除了对象外,GraphQL 类型系统还提供了如下类型:

枚举

types/enums.md
commit a75396846d9f8930d1e07e972a91ff59308e77cf

GraphQL 中的枚举是聚在一起表示一组值的字符串常量。通过自定义派生属性,可以将简单的 Rust 枚举转换为 GraphQL 枚举:

#[derive(juniper::GraphQLEnum)]
enum Episode {
    NewHope,
    Empire,
    Jedi,
}

# fn main() {}

Juniper 会将枚举变量转换为大写,因此这些变量对应的字符串值分别是 NEWHOPEEMPIREJEDI。如果你想要重写,可使用 graphql 属性,如同我们在对象定义学习的那样:

#[derive(juniper::GraphQLEnum)]
enum Episode {
    #[graphql(name="NEW_HOPE")]
    NewHope,
    Empire,
    Jedi,
}

# fn main() {}

文档化和弃用

就像定义对象一样,类型自身可以重命名和文档化。同样地,枚举变量可以重命名、文档化,以及弃用:

#[derive(juniper::GraphQLEnum)]
#[graphql(name="Episode", description="星球大战4:新希望")]
enum StarWarsEpisode {
    #[graphql(deprecated="对此,我们不做讨论")]
    ThePhantomMenace,

    #[graphql(name="NEW_HOPE")]
    NewHope,

    #[graphql(description="最好的一部")]
    Empire,
    Jedi,
}

# fn main() {}

接口

types/interfaces.md
commit 693405afa5a86df3a2277065696e7c42306ff630

GraphQL 接口能够正确地映射到常见的面向对象语言接口,如 Java 或 C#。但很不幸,Rust 并无与 GraphQL 接口正确映射的概念。因此,Juniper 中定义接口需要一点点范例代码;另一方面,可以做到让你完全控制所支持接口的类型。

为了突出展示在 Rust 中实现接口的不同方式,让我们看看不同实现方式所实现的相同结果:

特性(Traits)

特性(Traits)或许是你在 Rust 语言构建 GraphQL 接口时想使用的最明显概念。但是因为 GraphQL 支持向下转型(downcasting)而 Rust 却不支持,所以你必须手动实现如何将特性(trait)转换为具体类型。可以通过如下方式:

通过存取器方法向下转型

#[derive(juniper::GraphQLObject)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(juniper::GraphQLObject)]
struct Droid {
    id: String,
    primary_function: String,
}

trait Character {
    fn id(&self) -> &str;

    // 向下转型方法,每个具体类都需要实现
    fn as_human(&self) -> Option<&Human> { None }
    fn as_droid(&self) -> Option<&Droid> { None }
}

impl Character for Human {
    fn id(&self) -> &str { self.id.as_str() }
    fn as_human(&self) -> Option<&Human> { Some(&self) }
}

impl Character for Droid {
    fn id(&self) -> &str { self.id.as_str() }
    fn as_droid(&self) -> Option<&Droid> { Some(&self) }
}

juniper::graphql_interface!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| {
    field id() -> &str { self.id() }

    instance_resolvers: |_| {
        // 左边表示具体类型 T,右边是返回 Option<T> 的表达式
        &Human => self.as_human(),
        &Droid => self.as_droid(),
    }
});

# fn main() {}

instance_resolvers 闭包列出了给定接口的所有实现,以及如何解析接口。

如所看到的,使用特性(traits)意义不大:你需要列出 trait 自身的所有具体类型,且会有一些重复,啰里啰唆。

使用数据库查找

当具体类被请求时,如果你可以提供额外的数据库查询,则可以废弃向下转型方法,转而使用上下文。如下示例代码,我们将使用两个哈希表,你可以用两张表和一些 SQL 调用来代替:

# use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = Database)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(juniper::GraphQLObject)]
#[graphql(Context = Database)]
struct Droid {
    id: String,
    primary_function: String,
}

struct Database {
    humans: HashMap<String, Human>,
    droids: HashMap<String, Droid>,
}

impl juniper::Context for Database {}

trait Character {
    fn id(&self) -> &str;
}

impl Character for Human {
    fn id(&self) -> &str { self.id.as_str() }
}

impl Character for Droid {
    fn id(&self) -> &str { self.id.as_str() }
}

juniper::graphql_interface!(<'a> &'a Character: Database as "Character" where Scalar = <S> |&self| {
    field id() -> &str { self.id() }

    instance_resolvers: |&context| {
        &Human => context.humans.get(self.id()),
        &Droid => context.droids.get(self.id()),
    }
});

# fn main() {}

虽移除了向下转型方法,但代码仍有点啰嗦。

占位符(placeholder)对象

继续上段示例代码,trait 自身似乎没有必要,也许它可以仅是一个包含 ID 的结构体(struct)?

Continuing on from the last example, the trait itself seems a bit unneccesary. Maybe it can just be a struct containing the ID?

# use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = "Database")]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(juniper::GraphQLObject)]
#[graphql(Context = "Database")]
struct Droid {
    id: String,
    primary_function: String,
}

struct Database {
    humans: HashMap<String, Human>,
    droids: HashMap<String, Droid>,
}

impl juniper::Context for Database {}

struct Character {
    id: String,
}

juniper::graphql_interface!(Character: Database where Scalar = <S> |&self| {
    field id() -> &str { self.id.as_str() }

    instance_resolvers: |&context| {
        &Human => context.humans.get(&self.id),
        &Droid => context.droids.get(&self.id),
    }
});

# fn main() {}

减少了不少重复,但如果接口数据较多的情况下,此种做法不符合实际。

枚举

使用枚举和模式匹配介于使用特性(trait)和使用占位符(placeholder)对象之间。本例中,我们无需额外的数据库调用,因此移除。

#[derive(juniper::GraphQLObject)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(juniper::GraphQLObject)]
struct Droid {
    id: String,
    primary_function: String,
}

# #[allow(dead_code)]
enum Character {
    Human(Human),
    Droid(Droid),
}

juniper::graphql_interface!(Character: () where Scalar = <S> |&self| {
    field id() -> &str {
        match *self {
            Character::Human(Human { ref id, .. }) |
            Character::Droid(Droid { ref id, .. }) => id,
        }
    }

    instance_resolvers: |_| {
        &Human => match *self { Character::Human(ref h) => Some(h), _ => None },
        &Droid => match *self { Character::Droid(ref d) => Some(d), _ => None },
    }
});

# fn main() {}

输入对象

types/input_objects.md
commit 29025e6cae4a249fa56017dcf16b95ee4e89363e

输入对象是复杂的数据结构,可以用作 GraphQL 字段的参数。Juniper 中,可以使用自定义派生属性来定义输入对象,类似于定义简单对象、枚举:

#[derive(juniper::GraphQLInputObject)]
struct Coordinate {
    latitude: f64,
    longitude: f64
}

struct Root;
# #[derive(juniper::GraphQLObject)] struct User { name: String }

#[juniper::object]
impl Root {
    fn users_at_location(coordinate: Coordinate, radius: f64) -> Vec<User> {
        // 将坐标写入数据库
        // ...
# unimplemented!()
    }
}

# fn main() {}

文档化和重命名

类似于定义对象派生枚举对象,对类型和字段,既可以重命名,也可以添加文档:

#[derive(juniper::GraphQLInputObject)]
#[graphql(name="Coordinate", description="地球某一处")]
struct WorldCoordinate {
    #[graphql(name="lat", description="维度")]
    latitude: f64,

    #[graphql(name="long", description="精度")]
    longitude: f64
}

struct Root;
# #[derive(juniper::GraphQLObject)] struct User { name: String }

#[juniper::object]
impl Root {
    fn users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec<User> {
        // 将坐标写入数据库
        // ...
# unimplemented!()
    }
}

# fn main() {}

标量

types/scalars.md
commit 97e1005178b4bd4d96d85ffb7c82cd36b66380b3

GraphQL 查询中,标量是叶子节点的基本类型:ID、数字、字符串和布尔值。可以为其它基本值创建自定义标量,但这需要与你正在构建 API 的客户端库进行协调。

由于任何值都是以 JSON 格式传递,所以可用的数据类型也受到了限制。

自定义标量有两种方式。

  • 对于只封装基本类型的简单标量,可以使用自定义派生对象的新类型(newtype)方式。
  • 对于有自定义验证的高级用例标量,可以使用 graphql_scalar! 宏。

内建标量

Juniper 内建支持的标量有:

  • i32 表示 Int:有符号 32 位整数;
  • f64 表示 Float:有符号双精度浮点值;
  • String&str 表示 String:UTF‐8 字符序列;
  • bool 表示 Boolean:true 或者 false;
  • juniper::ID 表示 ID:此类型在规范中被定义为序列化字符串类型,但可以从字符串和整数解析。

第三方类型

Juniper 内建支持一些来自常用第三方包的附加类型,此特性支持默认开启。

  • uuid::Uuid
  • chrono::DateTime
  • url::Url

新类型(newtype)方式

通常情况下,你可能仅需要只包装已有类型的自定义标量。

这可以通过新类型(newtype)方式和自定义派生来实现,类似于 serde 的方式 #[serde(transparent)]

#[derive(juniper::GraphQLScalarValue)]
pub struct UserId(i32);

#[derive(juniper::GraphQLObject)]
struct User {
    id: UserId,
}

# fn main() {}

就这样简单,然后就可以在你的模式中使用 UserId

宏(macro)同样允许很多定制:

/// 可以使用文档注释指定描述。
#[derive(juniper::GraphQLScalarValue)]
#[graphql(
    transparent,
    // 可以重写 GraphQL 类型 name 属性
    name = "MyUserId",
    // 指定自定义描述
    // 属性中的描述将覆盖文档注释
    description = "这是自定义用户描述",
)]
pub struct UserId(i32);

# fn main() {}

自定义标量

对于需要自定义解析或验证的复杂情况,可以使用 graphql_scalar! 宏。

通常,将自定义标量表示为字符串。

下面的例子中,为自定义 Date 类型实现自定义标量。

注意:Juniper 通过 chrono 特性内建支持 chrono::DateTime 类型,为此目的此特性默认开启。

下面的例子仅为举例说明。

注意:本例假定 Date 类型实现 std::fmt::Displaystd::str::FromStr

# mod date { 
#    pub struct Date; 
#    impl std::str::FromStr for Date{ 
#        type Err = String; fn from_str(_value: &str) -> Result<Self, Self::Err> { unimplemented!() }
#    }
#    // 定义如何将日期表示为字符串
#    impl std::fmt::Display for Date {
#        fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
#            unimplemented!()
#        }
#    }
# }

use juniper::{Value, ParseScalarResult, ParseScalarValue};
use date::Date;

juniper::graphql_scalar!(Date where Scalar = <S> {
    description: "Date"

    // 定义如何将自定义标量转换为基本类型
    resolve(&self) -> Value {
        Value::scalar(self.to_string())
    }

    // 定义如何将基本类型解析为自定义标量
    from_input_value(v: &InputValue) -> Option<Date> {
        v.as_scalar_value::<String>()
         .and_then(|s| s.parse().ok())
    }

    // 定义如何解析字符串值
    from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, S> {
        <String as ParseScalarValue<S>>::from_str(value)
    }
});

# fn main() {}

联合

types/unions.md
commit 693405afa5a86df3a2277065696e7c42306ff630

从服务器视觉看,GraphQL 联合类似于接口:唯一的例外是联合自身不包含字段。

在Juniper中,graphql_union!接口宏(interface macro)具有相同的语法,但不支持定义字段。因此,接口中关于特性、占位符类型,或枚举使用,同样适用于联合。

如果查阅和接口章节相同的示例,我们将看到相似性和折衷性:

特性(Traits)

通过存取器方法向下转型

#[derive(juniper::GraphQLObject)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(juniper::GraphQLObject)]
struct Droid {
    id: String,
    primary_function: String,
}

trait Character {
    // 向下转型方法,每个具体类都需要实现
    fn as_human(&self) -> Option<&Human> { None }
    fn as_droid(&self) -> Option<&Droid> { None }
}

impl Character for Human {
    fn as_human(&self) -> Option<&Human> { Some(&self) }
}

impl Character for Droid {
    fn as_droid(&self) -> Option<&Droid> { Some(&self) }
}

juniper::graphql_union!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| { 
    instance_resolvers: |_| {
        // 左边表示具体类型 T,右边是返回 Option<T> 的表达式
        &Human => self.as_human(),
        &Droid => self.as_droid(),
    }
});

# fn main() {}

使用数据库查找

有毛病:此例代码还不能编译

# use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = Database)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(juniper::GraphQLObject)]
#[graphql(Context = Database)]
struct Droid {
    id: String,
    primary_function: String,
}

struct Database {
    humans: HashMap<String, Human>,
    droids: HashMap<String, Droid>,
}

impl juniper::Context for Database {}

trait Character {
    fn id(&self) -> &str;
}

impl Character for Human {
    fn id(&self) -> &str { self.id.as_str() }
}

impl Character for Droid {
    fn id(&self) -> &str { self.id.as_str() }
}

juniper::graphql_union!(<'a> &'a Character: Database as "Character" where Scalar = <S> |&self| {
    instance_resolvers: |&context| {
        &Human => context.humans.get(self.id()),
        &Droid => context.droids.get(self.id()),
    }
});

# fn main() {}

占位符(placeholder)对象

# use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = Database)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(juniper::GraphQLObject)]
#[graphql(Context = Database)]
struct Droid {
    id: String,
    primary_function: String,
}

struct Database {
    humans: HashMap<String, Human>,
    droids: HashMap<String, Droid>,
}

impl juniper::Context for Database {}

struct Character {
    id: String,
}

juniper::graphql_union!(Character: Database where Scalar = <S> |&self| {
    instance_resolvers: |&context| {
        &Human => context.humans.get(&self.id),
        &Droid => context.droids.get(&self.id),
    }
});

# fn main() {}

枚举

#[derive(juniper::GraphQLObject)]
struct Human {
    id: String,
    home_planet: String,
}

#[derive(juniper::GraphQLObject)]
struct Droid {
    id: String,
    primary_function: String,
}

# #[allow(dead_code)]
enum Character {
    Human(Human),
    Droid(Droid),
}

juniper::graphql_union!(Character: () where Scalar = <S> |&self| {
    instance_resolvers: |_| {
        &Human => match *self { Character::Human(ref h) => Some(h), _ => None },
        &Droid => match *self { Character::Droid(ref d) => Some(d), _ => None },
    }
});

# fn main() {}

模式(Schemas)

schema/schemas_and_mutations.md
commit 29025e6cae4a249fa56017dcf16b95ee4e89363e

模式由两种类型组成:查询对象和变更对象(Juniper 还不支持订阅),分别定义了字段查询和模式变更。

查询和变更都是常规的 GraphQL 对象,其定义类似于 Juniper 中的其它对象。不过变更对象是可选的,因为模式可以是只读性的。

Juniper 中,RootNode 类型表示一个模式。通常不需要你自己创建此对象:请参阅 IronRocket 的框架集成,了解模式与框架处理程序是如何被一起创建的。

模式首次创建时,Juniper 将遍历整个对象图,并注册所有类型。这意味着,如果定义了 GraphQL 对象但从未引用,那么此对象就不会暴露在模式中。

查询根(query root)

查询根(query root)也是 GraphQL 对象。其定义类似于 Juniper 中的其它对象,通常使用过程宏对象定义查询根:

# use juniper::FieldResult;
# #[derive(juniper::GraphQLObject)] struct User { name: String }
struct Root;

#[juniper::object]
impl Root {
    fn userWithUsername(username: String) -> FieldResult<Option<User>> {
        // 在数据库查找用户
# unimplemented!()
    }
}

# fn main() { }

变更

变更同样是 GraphQL 对象。变更是字段发生一些改变,如更新数据库。

# use juniper::FieldResult;
# #[derive(juniper::GraphQLObject)] struct User { name: String }
struct Mutations;

#[juniper::object]
impl Mutations {
    fn signUpUser(name: String, email: String) -> FieldResult<User> {
        // 验证输入并存储数据
# unimplemented!()
    }
}

# fn main() { }

服务器集成

servers/index.md
commit a75396846d9f8930d1e07e972a91ff59308e77cf

为了能够让 Juniper 与你自己选择的 HTTP 服务器一起使用,因此 Juniper 没有内建 HTTP 服务器。

第三方团队和 Juniper 官方提供了多个服务器集成的支持,供你结合实际场景来选择。

官方支持集成

servers/official.md
commit a75396846d9f8930d1e07e972a91ff59308e77cf

Juniper 提供了几种较受欢迎的 Rust 服务器的集成包。 libraries.

Hyper 集成

servers/hyper.md
commit 9623e4d32694118e68ce8706f29e2cfbc6c5b6dc

Hyper 是影响了许多 Rust Web 框架的敏捷 HTTP 实现。它通过 Tokio 运行时提供异步 I/O,并在 Rust 稳定版上工作。

Hyper 并非高层次 Web 框架,因此不包含一些通用特性,诸如:简单的终端路由、后端内建 HTTP 响应,以及可重用中间件等。对于 GraphQL 来说,这些并不是很大的不足,因为所有的 POSTs、GETs 通常都经过一个端点,并有一些响应明确定义的有效负载。

Juniper 的 Hyper 集成包为 juniper_hyper

!文件名 Cargo.toml

[dependencies]
juniper = "0.10"
juniper_hyper = "0.1.0"

GraphiQL 基本设定和源码实例

Warp 集成

servers/warp.md
commit c2f119690b683303dbabf2a8b029cff76b596728

Warp 是超简单、可组合、具有极快速度的 Web 服务器框架。Warp 基本构建块是过滤器:过滤器可组合表示需求多样的请求。Warp 构建在 Hyper 之上,运行于 Rust 稳定版。

Juniper 的 Warp 集成包为 juniper_warp

!文件名 Cargo.toml

[dependencies]
juniper = "0.10"
juniper_warp = "0.1.0"

GraphiQL 基本设定和源码实例

Rocket 集成

servers/rocket.md
commit 9623e4d32694118e68ce8706f29e2cfbc6c5b6dc

Rocket 是使 Web 应用开发变得简单高效和类型安全的 Rust Web 框架。Rocket 运行于 Rust 开发版,不能在 Rust 稳定版上工作。

Juniper 的 Rocket 集成包为 juniper_rocket

!文件名 Cargo.toml

[dependencies]
juniper = "0.10"
juniper_rocket = "0.2.0"

GraphiQL 基本设定和源码实例

Iron 集成

servers/iron.md
commit 29025e6cae4a249fa56017dcf16b95ee4e89363e

Iron 是在 Rust 领域已存在一段时间的可靠的库,具有常见的请求(request)/响应(response)/中间件(middleware)等体系结构,运行于 Rust 稳定版。

Juniper 的 Iron 集成包为 juniper_iron

!文件名 Cargo.toml

[dependencies]
juniper = "0.10"
juniper_iron = "0.2.0"

GraphiQL 基本设定和源码实例

第三方集成

servers/third-party.md
commit 9623e4d32694118e68ce8706f29e2cfbc6c5b6dc

以下几个优秀的 Rust Web 框架的 Juniper 集成包由第三方维护。

高阶应用

advanced/index.md
commit a75396846d9f8930d1e07e972a91ff59308e77cf

如下章节将介绍 Juniper 高阶应用场景。

内省

advanced/introspection.md
commit 29025e6cae4a249fa56017dcf16b95ee4e89363e

GraphQL 内建了一个特殊的顶级字段 __schema,查询此字段允许在运行时内省模式,以查看 GraphQL 服务器支持的查询(queries)和变更(mutations)。

内省查询是 GraphQL 常规查询,因此 Juniper 原生支持。例如,要获得支持类型的所有名称,可以对 Juniper 执行以下查询:

{
  __schema {
    types {
      name
    }
  }
}

模式内省输出为 JSON

GraphQL 生态中,许多客户端库和工具都需要完整的服务器模式描述。通常,描述是 JSON 格式的,被称为 schema.json。可以通过特别设计的内省查询生成模式的完整描述。

Juniper 提供函数来内省整个模式,将结果转换为 JSON,以便与 graphql-client 之类的工具和库一起使用:

use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};

// 定义模式(schema)

#[derive(juniper::GraphQLObject)]
struct Example {
  id: String,
}

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

struct Query;

#[juniper::object(
  Context = Context,
)]
impl Query {
   fn example(id: String) -> FieldResult<Example> {
       unimplemented!()
   }
}

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

fn main() {
    // 创建上下文对象
    let ctx = Context{};

    // 运行内建内省查询
    let (res, _errors) = juniper::introspect(
        &Schema::new(Query, EmptyMutation::new()),
        &ctx,
        IntrospectionFormat::default(),
    ).unwrap();

    // 转换内省结果为 JSON
    let json_result = serde_json::to_string_pretty(&res);
    assert!(json_result.is_ok());
}

非结构化对象

advanced/non_struct_objects.md
commit 29025e6cae4a249fa56017dcf16b95ee4e89363e

到目前为止,我们只介绍了结构化数据到 GraphQL 对象的映射。实际上,任何 Rust 类型都可以映射到 GraphQL 对象中。本章中,我们将介绍枚举(enums),请注意枚举特性——枚举不必映射到 GraphQL 接口即可使用。

利用类似 Result 的枚举报告错误信息是常用的方式,例如报告变更时的验证错误:

# #[derive(juniper::GraphQLObject)] struct User { name: String }

#[derive(juniper::GraphQLObject)]
struct ValidationError {
    field: String,
    message: String,
}

# #[allow(dead_code)]
enum SignUpResult {
    Ok(User),
    Error(Vec<ValidationError>),
}

#[juniper::object]
impl SignUpResult {
    fn user(&self) -> Option<&User> {
        match *self {
            SignUpResult::Ok(ref user) => Some(user),
            SignUpResult::Error(_) => None,
        }
    }

    fn error(&self) -> Option<&Vec<ValidationError>> {
        match *self {
            SignUpResult::Ok(_) => None,
            SignUpResult::Error(ref errors) => Some(errors)
        }
    }
}

# fn main() {}

我们使用枚举来决定用户输入的数据是否有效,枚举可以作为返回结果。例如,注册(GraphQL 变更)的结果信息。

虽然这是关于如何使用结构化数据之外的 Rust 类型来描述 GraphQL 对象的示例,但同时也是一个关于如何为“预期”错误(如验证错误)实现错误处理的示例。对于如何在 GraphQL 中描述错误,Juniper 并没有严格的规则。对如何设计“硬”字段错误以及如何进行预期错误建模,GraphQL 的一位作者提出了一些意见:客户端错误验证管理自定义用户错误的最佳方法

对象和泛型

advanced/objects_and_generics.md
commit 29025e6cae4a249fa56017dcf16b95ee4e89363e

GraphQL 和 Rust 的另一个差异是泛型。Rust 中,几乎任何类型都可以是泛型的——即接受类型参数。GraphQL 中,只有两种泛型类型:列表(lists)和非空值(non-nullables)。

这对从 Rust 向 GraphQL 暴露内容造成了限制:不能暴露泛型结构,而必须绑定类型参数。例如,你不能将 Result<T, E> 转换为 GraphQL 类型,但你能够Result<User, String> 转换为 GraphQL 类型。

让我们对非结构化对象中的示例做一些细小紧凑的改动,来进行泛型实现:

# #[derive(juniper::GraphQLObject)] struct User { name: String }
# #[derive(juniper::GraphQLObject)] struct ForumPost { title: String }

#[derive(juniper::GraphQLObject)]
struct ValidationError {
    field: String,
    message: String,
}

# #[allow(dead_code)]
struct MutationResult<T>(Result<T, Vec<ValidationError>>);

#[juniper::object(
    name = "UserResult",
)]
impl MutationResult<User> {
    fn user(&self) -> Option<&User> {
        self.0.as_ref().ok()
    }

    fn error(&self) -> Option<&Vec<ValidationError>> {
        self.0.as_ref().err()
    }
}

#[juniper::object(
    name = "ForumPostResult",
)]
impl MutationResult<ForumPost> {
    fn forum_post(&self) -> Option<&ForumPost> {
        self.0.as_ref().ok()
    }

    fn error(&self) -> Option<&Vec<ValidationError>> {
        self.0.as_ref().err()
    }
}

# fn main() {}

我们对 Result 做了包装,并暴露 Result<T, E> 的具体实例为不同的 GraphQL 对象。我们需要包装的原因是 Rust 具有派生特性的规则——本例中,Result 和 Juniper 的内部 GraphQL 特性都来自第三方。

因为我们使用泛型,所以还需要为实例化的类型指定一个名字。即使 Juniper 能够找出名字,MutationResult<User> 也不是有效的 GraphQL 类型名。

批量操作请求

advanced/multiple_ops_per_request.md
commit 9623e4d32694118e68ce8706f29e2cfbc6c5b6dc

GraphQL 标准通常假定,每个客户端操作(例如查询或变更)都有一次服务器请求。这在概念上很简单,但有可能效率低下。

一些客户端库——如apollo-link-batch-http,已经在单个 HTTP 请求中添加了批量操作请求的功能,以便于节省网络往返请求并提高性能。当然,在批量操作请求之前,应该进行权衡

Juniper 服务器集成包使用 JSON 数组支持单个 HTTP 请求中的批量操作请求,这样不需要任何特殊配置就能兼容支持客户端库的批量操作请求。

第三方维护的服务器集成包不需要支持批量操作请求,批量操作请求不属于 GraphQL 官方规范。

假定某个服务器集成支持批操作请求,现执行如下 GraphQL 查询:

{
  hero {
    name
  }
}

单个请求 POST 到服务器的 json 数据是:

{
  "query": "{hero{name}}"
}

单个请求响应数据如下:

{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

如果你想在一个 HTTP 请求中运行两次相同的查询,那么要 POST 到服务器的批量 JSON 数据是:

[
  {
    "query": "{hero{name}}"
  },
  {
    "query": "{hero{name}}"
  }
]

批量操作请求响应数据如下:

[
  {
    "data": {
      "hero": {
        "name": "R2-D2"
      }
    }
  },
  {
    "data": {
      "hero": {
        "name": "R2-D2"
      }
    }
  }
]

Juniper 中文手册(同步最新开发版)

Juniper 中文手册包含 Juniper 中文文档和代码示例,源码放在 zzy/juniper-book-zh,内容译自官方文档

💥 更新时间:2019-09-02


做贡献

需求

本手册由 mdBook 编译而成。

如果已有 Rust 环境,安装 mdBook 请执行命令:

cargo install mdbook

启动本地测试服务器

启动持续编译手册并自动加载页面的本地测试服务器,执行命令:

mdbook serve

生成手册

将手册渲染输出为 HTML,执行命令:

mdbook build

输出目录为:./docs

测试

测试手册中的所有代码示例,运行命令:

cd ./tests
cargo test

测试配置

手册中的所有 Rust 代码示例在 CI 上编译,使用了 skeptic 库。

声明

水平有限,错漏难免,欢迎指教;或请发 issue到GitHub;或者直接联系。

电子邮箱:linshi@budshome.com;微信:cd-zzy;QQ:9809920。

感谢graphql-rust/juniper 团队的无私奉献。

💥 笔者无意侵犯任何人的权利和利益,故若有不适,请联系我。


Rust 语言术语中英文对照表

引用自 Rust 中文翻译项目组 - Rust 语言术语中英文对照表

English 英文Chinese 中文Note 备注
A
Abstract Syntax Tree抽象语法树
ABI应用程序二进制接口Application Binary Interface 缩写
accumulator累加器
accumulator variable累加器变量
ahead-of-time compiled预编译
ahead-of-time compiled language预编译语言
alias别名
aliasing别名使用参见 Wikipedia
angle brackets尖括号,“<”和“>”
annotate标注,注明(动词)
annotation标注,注明(名词)
ARC原子引用计数器Atomic Referecne Counter
anonymity匿名
argument参数,实参,实际参数不严格区分的话, argument(参数)和
parameter(参量)可以互换地使用
argument type参数类型
assignment赋值
associated functions关联函数
associated items关联项
associated types关联类型
asterisk星号(*)
atomic原子的
attribute属性
automated building自动构建
automated test自动测试,自动化测试
B
baroque macro巴洛克宏
benchmark基准
binary二进制的
binary excutable二进制的可执行文件
bind绑定
block语句块,代码块
boolean布尔型,布尔值
borrow check借用检查
borrower借用者,借入者
borrowing借用
bound约束,限定,限制此词和 constraint 意思相近,
constraint 在 C# 语言中翻译成“约束”
box箱子,盒子,装箱类型一般不译,作动词时翻译成“装箱”,
具有所有权的智能指针
boxed装箱,装包
boxing装箱,装包
brace大括号,“{”或“}”
buffer缓冲,缓冲区,缓冲器,缓存
build构建
builder pattern创建者模式
C
call调用
caller调用者
capacity容器
capture捕获
cargo(Rust 包管理器,不译)该词作名词时意思是“货物”,
作动词时意思是“装载货物”
cargo-fyCargo 化,使用 Cargo 创建项目
case analysis事例分析
cast类型转换,转型
casting类型转换
chaining method call链式方法调用
channel信道,通道
closure闭包
coercion强制类型转换,强制转换coercion 原意是“强制,胁迫”
collection集合参见 Wikipedia
combinator组合算子,组合器
comma逗号,“,”
command命令
command line命令行
comment注释
compile编译(动词)
compile time编译期,编译期间,编译时
compilation编译(名词)
compilation unit编译单元
compiler编译器
compiler intrinsics编译器固有功能
compound复合(类型,数据)
concurrency并发
conditional compilation条件编译
configuration配置
constructor构造器
consumer消费者
container容器
container type容器类型
convert转换,转化,转
copy复制,拷贝
crate包,包装箱,装包一般不译,crate 是 Rust 的基本编译单元
curly braces大括号,包含“{”和“}”
custom type自定义类型
D
dangling pointer悬垂指针use after free 在释放后使用
data race数据竞争
dead code死代码,无效代码,不可达代码
deallocate释放,重新分配
declare声明
dependency依赖
deref coercions强制多态
dereference解引用Rust 文章中有时简写为 Deref
derive派生
designator指示符
destruction销毁,毁灭
destructor析构器,析构函数
destructure解构
destructuring解构,解构赋值
desugar脱糖
diverge function发散函数
device drive设备驱动
directory目录
dispatch分发
diverging functions发散函数
documentation文档
dot operator点运算符
DST动态大小类型dynamic sized type,一般不译,
使用英文缩写形式
dynamic language动态类型语言
dynamic trait type动态 trait 类型
E
encapsulation封装
equality test相等测试
elision省略
exhaustiveness checking穷尽性检查,无遗漏检查
expression表达式
expression-oriented language面向表达式的语言
explicit显式
explicit discriminator显式的辨别值
explicit type conversion显式类型转换
extension扩展名
extern外,外部作关键字时不译
F
fat pointer胖指针
feature gate功能开关
field字段
field-level mutability字段级别可变性
file文件
fmt格式化,是 format 的缩写
formatter格式化程序,格式化工具,格式器
floating-point number浮点数
flow control流程控制
Foreign Function Interface(FFI)外部语言函数接口
fragment specifier片段分类符
free variables自由变量
freeze冻结
function函数
function declaration函数声明
functional函数式
G
garbage collector垃圾回收
generalize泛化,泛型化
generator生成器
genericf泛型
generic type泛型类型
growable可增长的
guard守卫
H
handle error句柄错误
hash哈希,哈希值,散列
hash map散列映射,哈希表
heap
hierarchy层次,分层,层次结构
higher rank lifetime高阶生命周期
higher rank trait bound高阶 trait 约束
higher tank type高阶类型
hygiene卫生
hygienic macro system卫生宏系统
I
ICE编译内部错误internal comppiler error 的缩写
immutable不可变的
implement实现
implementor实现者
implicit隐式
implicit discriminator隐式的辨别值
implicit type conversion隐式类型转换
import导入
in assignment在赋值(语句)
index索引英语复数形式:indices
infer推导(动词)
inference推导(名词)
inherited mutability承袭可变性
inheritance继承
integrated development
environment(IDE)
集成开发环境中文著作中通常直接写成 IDE
integration-style test集成测试
interior mutablity内部可变性
installer安装程序,安装器
instance实例
instance method实例方法
integer整型,整数
interact相互作用,相互影响
interior mutability内部可变性
intrinsic固有的
invoke调用
item项,条目,项目
iterate重复
iteration迭代
iterator迭代器
iterator adaptors迭代器适配器
iterator invalidation迭代器失效
L
LHS左操作数left-hand side 的非正式缩写,
与 RHS 相对
lender借出者
library
lifetime生存时间,寿命,生命周期
lifetime elision生命周期省略
link链接
linked-list链表
lint(不译)lint 英文本义是“纱布,绒毛”,此词在
计算机领域中表示程序代码中可疑和
不具结构性的片段,参见 Wikipedia
list
literal数据,常量数据,字面值,字面量,
字面常量,字面上的
英文意思:字面意义的(内容)
LLVM(不译)Low Level Virtual Machine 的缩写,
是构建编译器的系统
loop循环作关键字时不译
low-level code底层代码
low-level language底层语言
l-value左值
M
main functionmain 函数,主函数
macro
map映射一般不译
match guard匹配守卫
memory内存
memory leak内存泄露
memory safe内存安全
meta原则,元
metadata元数据
metaprogramming元编程
metavariable元变量
method call syntax方法调用语法
method chaining方法链
method definition方法定义
modifier修饰符
module模块
monomorphization单态mono: one, morph: form
move移动,转移按照 Rust 所规定的内容,
英语单词 transfer 的意思
比 move 更贴合实际描述
参考:Rust by Example
move semantics移动语义
mutability可变性
mutable可变
mutable reference可变引用
multiple bounds多重约束
mutiple patterns多重模式
N
nest嵌套
Nightly RustRust 开发版nightly本意是“每夜,每天晚上”,
指代码每天都更新
NLL非词法生命周期non lexecal lifetime 的缩写,
一般不译
non-copy type非复制类型
non-generic非泛型
no-op空操作,空运算(此词出现在类型转换章节中)
non-commutative非交换的
non-scalar cast非标量转换
notation符号,记号
numeric数值,数字
O
optimization优化
out-of-bounds accessing越界访问
orphan rule孤儿规则
overflow溢出,越界
own占有,拥有
owner所有者,拥有者
ownership所有权
P
package manager包管理器,软件包管理器
panic(不译)此单词直接翻译是“恐慌”,
在 Rust 中用于不可恢复的错误处理
parameter参量,形参,形式参量不严格区分的话, argument(参数)和
parameter(参量)可以互换地使用
parametric polymorphism参数多态
parent scope父级作用域
parentheses小括号,包括“(”和“)”
parse分析,解析
parser(语法)分析器,解析器
pattern模式
pattern match模式匹配
phantom type虚类型,虚位类型phantom 相关的专有名词:
phantom bug 幻影指令
phantom power 幻象电源
参见:HaskellHaskell/Phantom_type
Rust/Phantomstdlib/PhantomData
platform平台
polymorphism多态
powershell(不译)Windows 系统的一种命令行外壳程序
和脚本环境
possibility of absence不存在的可能性
precede预先?,在...发生(或出现)
prelude(不译)预先导入模块,英文本意:序曲,前奏
primitive types原生类型,基本类型,简单类型
print打印
process进程
procedural macros过程宏,程序宏
project项目,工程
prototype原型
R
race condition竞态条件
RAII资源获取即初始化(一般不译)resource acquisition is initialization 的缩写
range区间,范围
raw identifier原始标识符
raw pointer原始指针,裸指针
RC引用计数reference counted
Reader读取器
recursive macro递归宏
reference引用
release发布
resource资源
resource leak资源泄露
RHS右操作数right-hand side 的非正式缩写,
与 LHS 相对
root directory根目录
runtime运行时
runtime behavior运行时行为
runtime overhead运行时开销
Rust(不译)一种编程语言
Rustacean(不译)编写 Rust 的程序员或爱好者的通称
rustc(不译)Rust 语言编译器
r-value右值
S
scalar标量,数量
schedule调度
scope作用域
screen屏幕
script脚本
semicolon分号,“;”
self自身,作关键字时不译
shadowing遮敝,隐蔽,隐藏,覆盖
signature标记
slice切片
snake case蛇形命名参见:Snake case
source file源文件
source code源代码
specialization泛型特化
square平方,二次方,二次幂
square brackets中括号,“[”和“]”
src(不译)source 的缩写,指源代码
stack
stack unwind栈中国
statement语句
statically allocated静态分配
statically allocated string静态分配的字符串
statically dispatch静态分发
static method静态方法
string字符串
string literal字符串常量
string slices字符串片段
stringify字符串化
subscript notation下标
sugar
super父级,作关键字时不译
syntax context语法上下文
systems programming language系统级编程语言
T
tagged union标记联合
target triple多层次指标,三层/重 指标/目标triple 本义是“三”,但此处虚指“多”,
此词翻译需要更多讨论
terminal终端
testing测试
testsuit测试套件
the least significant bit (LSB)最低数字位
the most significant bit (MSB)最高数字位
thread线程
TOML(不译)Tom’s Obvious, Minimal Language
的缩写,一种配置语言
token tree令牌树?待进一步斟酌
trait(不译)其字面上有“特性,特征”之意,不作翻译
trait boundtrait 约束bound 有“约束,限制,限定”之意
trait objecttrait 对象
transmute(不译)其字面上有“变化,变形,变异”之意,
不作翻译
trivial平凡的
troubleshooting疑难解答,故障诊断,
故障排除,故障分析
tuple元组
two’s complement补码,二补数
two-word object双字对象
type annotation类型标注
type erasure类型擦除
type inference类型推导
type inference engine类型推导引擎
type parameter类型参量
type placeholder类型占位符
type signature类型标记
U
undefined behavior未定义行为
uninstall卸载
unit-like struct类单元结构体
unit struct单元结构体
“unit-style” tests单元测试
unit test单元测试
unit type单元类型
universal function call syntax
(UFCS)
通用函数调用语法
unsized types不定长类型
unwind展开
unwrap解包暂译!
V
variable binding变量绑定
variable shadowing变量遮蔽,变量隐蔽,
变量隐藏,变量覆盖
variable capture变量捕获
variant变量
vector(动态数组,一般不译)vector 本义是“向量”
visibility可见性
vtable虚表
W
where clausewhere 子句,where 从句,where 分句在数据库的官方手册中多翻译成“子句”,英语语法中翻译成“从句”
wrap包裹暂译!
wrapped装包
wrapper装包
Y
yield产生(收益、效益等),产出,提供
Z
zero-cost abstractions零开销抽象
zero-width space(ZWSP)零宽空格