Rust的GraphQL编译器库apollo-compiler的使用:支持高效解析、验证和操作GraphQL Schema与查询

Rust的GraphQL编译器库apollo-compiler的使用:支持高效解析、验证和操作GraphQL Schema与查询

apollo-compiler是一个用于GraphQL查询语言的基于查询的编译器库。

特性

  • 提供相对低级的GraphQL语法AST,以及高级的SchemaExecutableDocument表示
  • 所有三种表示都可以解析(内部使用apollo-parser)、程序化创建或修改,并序列化
  • 根据GraphQL规范验证Schema和可执行文档
  • 执行查询的模式自省部分

快速开始

添加依赖开始使用apollo-compiler:

cargo add apollo-compiler

或者手动添加到Cargo.toml:

[dependencies]
apollo-compiler = "1.28.0"

使用示例

基础使用示例:

use apollo_compiler::Schema;
use apollo_compiler::ExecutableDocument;

let sdl = r#"
  type Query {
    field: Int
  }
"#;
let query = "{ field }";

/// 如果验证失败,panic消息会很好地格式化指向源文件的相关部分
let schema = Schema::parse_and_validate(sdl, "sdl.graphql").unwrap();
let doc = ExecutableDocument::parse_and_validate(&schema, query, "query.graphql").unwrap();

完整示例

访问片段定义字段类型

use apollo_compiler::{Schema, ExecutableDocument, Node, executable};

let schema_input = r#"
type User {
  id: ID
  name: String
  profilePic(size: Int): URL
}

schema { query: User }

scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
"#;
let query_input = r#"
query getUser {
  ... vipCustomer
}

#fragment definition where we want to know the field types.
fragment vipCustomer on User {
  id
  name
  profilePic(size: 50)
}
"#;

let schema = Schema::parse_and_validate(schema_input, "schema.graphql").unwrap();
let document = ExecutableDocument::parse_and_validate(&schema, query_input, "query.graphql")
    .unwrap();

let op = document.operations.get(Some("getUser")).expect("getUser query does not exist");
let fragment_in_op = op.selection_set.selections.iter().filter_map(|sel| match sel {
    executable::Selection::FragmentSpread(spread) => {
        Some(document.fragments.get(&spread.fragment_name)?.as_ref())
    }
    _ => None
}).collect::<Vec<&executable::Fragment>>();

let fragment_fields = fragment_in_op.iter().flat_map(|frag| {
    frag.selection_set.fields()
}).collect::<Vec<&Node<executable::Field>>>();
let field_ty = fragment_fields
    .iter()
    .map(|f| f.ty().inner_named_type().as_str())
    .collect::<Vec<&str>>();
assert_eq!(field_ty, ["ID", "String", "URL"]);

获取查询操作定义中使用的字段上定义的指令

use apollo_compiler::{Schema, ExecutableDocument, Node, executable};

let schema_input = r#"
type Query {
  topProducts: Product
}

type Product {
  inStock: Boolean @join__field(graph: INVENTORY)
  name: String @join__field(graph: PRODUCTS)
}

enum join__Graph {
  INVENTORY,
  PRODUCTS,
}
directive @join__field(graph: join__Graph) on FIELD_DEFINITION
"#;
let query_input = r#"
query getProduct {
  topProducts {
    inStock
  }
}
"#;

let schema = Schema::parse_and_validate(schema_input, "schema.graphql").unwrap();
let document = ExecutableDocument::parse_and_validate(&schema, query_input, "query.graphql")
    .unwrap();

let get_product_op = document
    .operations
    .get(Some("getProduct"))
    .expect("getProduct query does not exist");

let in_stock_field = &get_product_op
    .selection_set
    .fields()
    .find(|f| f.name == "topProducts")
    .expect("topProducts field does not exist")
    .selection_set
    .fields()
    .find(|f| f.name == "inStock")
    .expect("inStock field does not exist")
    .definition;
let in_stock_directive: Vec<_> = in_stock_field
    .directives
    .iter()
    .map(|dir| &dir.name)
    .collect();
assert_eq!(in_stock_directive, ["join__field"]);

打印有错误的GraphQL文档的诊断信息

use apollo_compiler::parser::Parser;

let input = "{ ... }";

if let Err(diagnostics) = Parser::new().parse_mixed_validate(input, "document.graphql") {
    println!("{diagnostics}")
}

许可证

根据以下任一许可证授权:

  • Apache License, Version 2.0
  • MIT license

1 回复

Rust的GraphQL编译器库apollo-compiler使用指南

apollo-compiler是Apollo推出的一个Rust库,用于高效解析、验证和操作GraphQL schema与查询。它提供了强大的工具来处理GraphQL文档,包括类型系统定义和查询验证。

主要特性

  • 解析GraphQL schema和查询文档
  • 验证GraphQL文档的正确性
  • 提供schema构建API
  • 支持schema和查询的转换操作
  • 高效的错误报告

安装

在Cargo.toml中添加依赖:

[dependencies]
apollo-compiler = "0.11"

基本使用方法

1. 解析GraphQL Schema

use apollo_compiler::Schema;

let input = r#"
type Query {
  hello: String
  user(id: ID!): User
}

type User {
  id: ID!
  name: String
}
"#;

let schema = Schema::parse(input, "schema.graphql").unwrap();

2. 解析GraphQL查询

use apollo_compiler::ExecutableDocument;

let query = r#"
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}
"#;

let doc = ExecutableDocument::parse(query, "query.graphql").unwrap();

3. 验证查询与Schema的兼容性

use apollo_compiler::validation;

let schema = Schema::parse(/* schema内容 */, "schema.graphql").unwrap();
let doc = ExecutableDocument::parse(/* 查询内容 */, "query.graphql").unwrap();

let diagnostics = validation::validate(&schema, &doc);
if diagnostics.is_empty() {
    println!("查询有效!");
} else {
    for diagnostic in diagnostics {
        println!("{}", diagnostic);
    }
}

4. 以编程方式构建Schema

use apollo_compiler::{Schema, ast};
use apollo_compiler::Node;

let mut schema = Schema::new();
schema.schema_definition.make_schema_definition();

// 添加Query类型
let query_type = schema.types.entry("Query".into()).or_default();
query_type.make_object_type_definition();

// 添加hello字段
let hello_field = ast::FieldDefinition {
    name: Node::new("hello".into()),
    ty: Node::new(ast::Type::Named("String".into())),
    arguments: Vec::new(),
    directives: Vec::new(),
    description: None,
};
query_type
    .as_object_mut()
    .unwrap()
    .fields
    .insert("hello".into(), Node::new(hello_field));

5. 转换和操作Schema

use apollo_compiler::Schema;

let schema = Schema::parse(/* schema内容 */, "schema.graphql").unwrap();

// 获取所有类型名
let type_names: Vec<_> = schema.types.keys().collect();
println!("Types: {:?}", type_names);

// 获取特定类型的字段
if let Some(user_type) = schema.types.get("User") {
    if let Some(fields) = user_type.as_object().map(|obj| &obj.fields) {
        for (field_name, _) in fields {
            println!("User field: {}", field_name);
        }
    }
}

高级用法

自定义验证规则

use apollo_compiler::{Schema, ExecutableDocument};
use apollo_compiler::validation::{ValidatorContext, visit, Visitor};

struct MyCustomRule;

impl Visitor for MyCustomRule {
    fn enter_field(&mut self, ctx: &mut ValidatorContext<'_>, field: &ast::Field) {
        if field.name.as_str() == "secretField" {
            ctx.report_error("不允许查询secretField", field.span());
        }
    }
}

let schema = Schema::parse(/* schema内容 */, "schema.graphql").unwrap();
let doc = ExecutableDocument::parse(/* 查询内容 */, "query.graphql").unwrap();

let mut ctx = ValidatorContext::new(&schema, &doc);
let mut visitor = MyCustomRule;
visit(&mut visitor, &mut ctx);

生成GraphQL文档字符串

use apollo_compiler::Schema;

let schema = Schema::parse(/* schema内容 */, "schema.graphql").unwrap();

// 将schema转换回GraphQL字符串
let schema_str = schema.to_string();
println!("{}", schema_str);

错误处理

apollo-compiler提供了详细的错误信息:

let result = Schema::parse("type Query { hello: String", "broken.graphql");

match result {
    Ok(schema) => println!("Schema解析成功"),
    Err(errors) => {
        for error in errors {
            println!(
                "错误在 {}:{}:{} - {}",
                error.location().file_name(),
                error.location().line_number(),
                error.location().column_number(),
                error.message()
            );
        }
    }
}

性能提示

  • 对于大型schema,考虑重用Schema实例
  • 使用Arc共享schema以减少克隆
  • 批量处理验证以减少重复工作

apollo-compiler是处理GraphQL的强大工具,特别适合需要高性能和精确控制的Rust应用程序。

完整示例代码

use apollo_compiler::{Schema, ExecutableDocument, validation};
use apollo_compiler::ast::{self, FieldDefinition};
use apollo_compiler::Node;
use std::sync::Arc;

fn main() {
    // 示例1: 解析Schema
    let schema_input = r#"
    type Query {
        books: [Book!]!
        authors: [Author!]!
    }
    
    type Book {
        id: ID!
        title: String!
        author: Author!
    }
    
    type Author {
        id: ID!
        name: String!
        books: [Book!]!
    }
    "#;
    
    let schema = Schema::parse(schema_input, "library.graphql").unwrap();
    
    // 示例2: 解析查询
    let query_input = r#"
    query GetBooks {
        books {
            title
            author {
                name
            }
        }
    }
    "#;
    
    let document = ExecutableDocument::parse(query_input, "query.graphql").unwrap();
    
    // 示例3: 验证查询
    let diagnostics = validation::validate(&schema, &document);
    if !diagnostics.is_empty() {
        for diag in diagnostics {
            println!("验证错误: {}", diag);
        }
        return;
    }
    
    // 示例4: 编程方式构建Schema
    let mut new_schema = Schema::new();
    new_schema.schema_definition.make_schema_definition();
    
    // 添加Product类型
    let product_type = new_schema.types.entry("Product".into()).or_default();
    product_type.make_object_type_definition();
    
    // 添加Product字段
    let id_field = FieldDefinition {
        name: Node::new("id".into()),
        ty: Node::new(ast::Type::Named("ID!".into())),
        arguments: Vec::new(),
        directives: Vec::new(),
        description: None,
    };
    
    let name_field = FieldDefinition {
        name: Node::new("name".into()),
        ty: Node::new(ast::Type::Named("String!".into())),
        arguments: Vec::new(),
        directives: Vec::new(),
        description: None,
    };
    
    product_type
        .as_object_mut()
        .unwrap()
        .fields
        .insert("id".into(), Node::new(id_field));
        
    product_type
        .as_object_mut()
        .unwrap()
        .fields
        .insert("name".into(), Node::new(name_field));
    
    // 打印生成的Schema
    println!("生成的Schema:\n{}", new_schema.to_string());
    
    // 示例5: 使用Arc共享Schema
    let shared_schema = Arc::new(schema);
    
    // 在多线程环境中使用共享Schema
    let schema_clone = shared_schema.clone();
    std::thread::spawn(move || {
        println!("在子线程中访问类型数量: {}", schema_clone.types.len());
    }).join().unwrap();
}
回到顶部