libgraphql-parser 0.0.5

A blazing fast, error-focused, lossless GraphQL parser for schema, executable, and mixed documents.
Documentation

Features

  • Error-resilient parsing — documents produce a partial AST alongside a list of errors, even when the input is malformed. Never panics.
  • Rust-inspired error output — error messages with source snippets, span highlighting, contextual notes, and fix suggestions.
  • Blazing fast - Perf metrics exceeding apollo_parser (v0.8.4) and matching or exceeding graphql_parser (v0.4.1) on all fixtures.
  • Zero-copy lexing — uses Cow<'src, str> to avoid allocations for tokens that match the source verbatim.
  • Parse schema, executable, and mixed documents — parses type definitions, operations/fragments, or documents containing both interleaved together.
  • September 2025 GraphQL specification compliance.
  • Dual column tracking — reports both UTF-8 character positions (for display) and UTF-16 code unit positions (for LSP integration).
  • Comment/trivia preservation — captures comments and other trivia as "preceding trivia" attached to tokens.
  • Generic over token sources — the parser works with any GraphQLTokenSource (string input, Rust proc-macro token stream input, etc.).
  • Configurable AST accessvalid() API for strict consumers that require error-free input, ast() for best-effort tooling that needs error-recovery (IDEs, linters, formatters, etc).
  • Fuzz-tested at scale70M+ libfuzzer executions across 4 fuzz targets, zero crashes.
  • Lossless syntax tree — every AST node carries byte-offset spans; combined with the SourceMap, the original source text can be reconstructed losslessly via AstNode::append_source().
  • graphql-parser v0.4 compatibility — bidirectional conversion between libgraphql-parser's AST and the graphql_parser v0.4 AST via the compat module.

Coming soon:

  • Drop-in compat with apollo-parser — translation utils to make it easy to integrate with tools that already depend on the apollo_parser::cst AST structure.

Getting Started

cargo add libgraphql-parser

Or add this to your Cargo.toml:

[dependencies]
libgraphql-parser = "0.0.4"

Usage

Parse a GraphQL schema document:

use libgraphql_parser::GraphQLParser;

let result = GraphQLParser::new("type Query { hello: String }")
    .parse_schema_document();

assert!(!result.has_errors());
let (doc, _) = result.valid().unwrap();

Parse an executable document (queries, mutations, subscriptions):

use libgraphql_parser::GraphQLParser;

let result = GraphQLParser::new("{ user { name email } }")
    .parse_executable_document();

assert!(!result.has_errors());

Get the line and column of a node:

use libgraphql_parser::GraphQLParser;

let result = GraphQLParser::new("type Query { hello: String }")
    .parse_schema_document();
let (doc, source_map) = result.valid().unwrap();

let query_def_node = &doc.definitions[0];
let source_span = query_def_node.source_span(&source_map).unwrap();
// line_col() returns 0-based (line, column) pairs where columns
// are measured in UTF-8 characters (not bytes or UTF-16 units).
let ((start_line, start_col), (end_line, end_col)) = source_span.line_col();

Error Recovery

Unlike many GraphQL parsers that stop at the first error, libgraphql-parser collects multiple errors and still produces a best-effort AST. This is essential for IDE integration, linters, and formatters:

use libgraphql_parser::GraphQLParser;

// This schema has errors: missing `:` on field type, unclosed brace
let source = "type Query { hello String";
let result = GraphQLParser::new(source).parse_schema_document();

// Errors are collected — inspect them all at once
assert!(result.has_errors());
for error in result.errors() {
    eprintln!("{}", error.format_detailed(Some(source)));
}

// A partial AST is always available for best-effort tooling
let doc = result.ast();
// IDE completions, formatting, linting can still work
// on the partially-parsed document
println!("Parsed {} definitions", doc.definitions.len());

Diagnostic Output

Error messages include source spans, contextual notes, and fix suggestions — inspired by the Rust compiler's diagnostic style:

error: unknown directive location `FIELD_DEFINTION`
  --> <input>:1:42
   |
 1 | directive @deprecated(reason: String) on FIELD_DEFINTION | ENUM_VALUE
   |                                          ^^^^^^^^^^^^^^^
   = help: did you mean `FIELD_DEFINITION`?

Unclosed delimiters point back to the opening location:

error: unclosed `{`
  --> <input>:9:2
   |
 9 | }
   |  ^
   = note: opening `{` in selection set here
      1 | query {
        |       -

Strict vs. Best-Effort AST Access

ParseResult offers two modes for accessing the AST:

use libgraphql_parser::GraphQLParser;

let source = "type Query { hello: String }";
let result = GraphQLParser::new(source).parse_schema_document();

// Strict mode: returns AST only if there were zero errors.
// Use this when compiling schemas or executing queries.
if let Some((doc, _source_map)) = result.valid() {
    // Guaranteed: no parse errors
}

// Best-effort mode: AST is always available, even with errors.
// Use this for IDE features, formatters, and linters.
let doc = result.ast();
// May be a partial/recovered AST — check result.has_errors()

Design Goals

  • Performance — zero-copy lexing via Cow<'src, str>, minimal allocations (e.g., SmallVec for trivia), and hand-written recursive descent parsing.
  • Error resilience — always produce as much AST as possible, collecting all errors in a single pass rather than stopping at the first failure.
  • Spec correctness — targets the September 2025 GraphQL specification.
  • Extensible architecture — the parser is generic over GraphQLTokenSource, enabling the same parsing logic to work across string input, proc-macro token streams, and other token sources.
  • Tooling-ready — designed for IDE integration, linters, formatters, and compiler frontends with dual UTF-8/UTF-16 position tracking and configurable AST access.

Comparison to Alternatives

libgraphql-parser graphql-parser apollo-parser cynic-parser async-graphql-parser
Spec version Sep 2025 Oct 2016 Oct 2021 Oct 2021 Oct 2021
Error recovery ✅ Partial AST + errors ❌ Fail-fast ✅ Full CST + errors ✅ Multiple errors ❌ Fail-fast
Zero-copy lexing Cow<'src, str> &'str
Output type Lossless AST Lossy AST Lossless CST Lossy AST (arena) Lossy AST
Mixed documents
Trivia preserved ✅ Comments ✅ All whitespace
GitHub schema parse 8.33 ms 8.53 ms 12.0 ms ?? ??

Performance

Performance is a first-class design goal. The lexer avoids allocations via Cow<'src, str> (borrowing directly from the source string for tokens that don't need transformation) and uses SmallVec for trivia storage and token-buffering. The parser itself is a hand-written recursive descent parser, avoiding the overhead of parser generator runtimes and allowing for more helpful and structured error information, notes, and possible-fix suggestions.

Benchmarks run via Criterion on synthetic schemas (small ~1.5KB, medium ~106KB, large ~500KB), vendored real-world schemas (Star Wars ~4KB, GitHub ~1.2MB), and a locally-fetched real-world schema (Shopify Admin ~3.1MB), plus executable documents.

You can run these benchmarks yourself with cargo bench --package libgraphql-parser, or use the high-confidence script which will also aggregate results in a table like below: ./crates/libgraphql-parser/scripts/run-benchmarks.sh.

Measured: 2026-03-19 on Apple M2 Max (arm64), 64 GB RAM, macOS, rustc 1.90.0-nightly (0d9592026 2025-07-19), --release profile. Comparison parsers: graphql-parser 0.4.1, apollo-parser 0.8.4. All values are Criterion point estimates at a 99% confidence level.

Schema Parsing

Input libgraphql-parser graphql-parser apollo-parser
small (~1.5 KB) 28.8 µs 43.6 µs 45.4 µs
medium (~106 KB) 1.61 ms 1.92 ms 2.01 ms
large (~500 KB) 8.04 ms 8.84 ms 9.46 ms
starwars (~4 KB) 34.7 µs 49.3 µs 54.1 µs
github (~1.2 MB) 8.33 ms 8.53 ms 12.0 ms
shopify_admin (~3.1 MB) 14.7 ms 16.7 ms 24.6 ms

Executable Document Parsing

Input libgraphql-parser graphql-parser apollo-parser
simple query 1.78 µs 2.81 µs 2.96 µs
complex query 28.7 µs 38.9 µs 38.0 µs

Lexer Throughput

Input Time Throughput
small (~1.5 KB) 6.81 µs ~331 MiB/s
medium (~106 KB) 318 µs ~317 MiB/s
large (~500 KB) 1.48 ms ~322 MiB/s
starwars (~4 KB) 9.02 µs ~440 MiB/s
github (~1.2 MB) 2.06 ms ~568 MiB/s
shopify_admin (~3.1 MB) 3.68 ms ~840 MiB/s

Core Types

Type Description
GraphQLParser<S> Generic recursive-descent parser. Entry points: parse_schema_document(), parse_executable_document(), parse_mixed_document().
ParseResult<T> Result type holding both a (possibly partial) AST and accumulated errors.
StrGraphQLTokenSource Zero-copy lexer producing GraphQLToken streams from &str input.
GraphQLParseError Parse error with message, source span, categorized kind, and contextual notes.
GraphQLTokenSource Trait for pluggable token sources (string input, proc-macro tokens, etc.).

Part of the libgraphql Ecosystem

libgraphql-parser is the parsing foundation of the libgraphql project — a comprehensive GraphQL engine library for building tools, clients, and servers in Rust. It is used by libgraphql-core for schema building, operation validation, and type system logic.

Running Tests

cargo test --package libgraphql-parser

Fuzz Testing

The crate includes a cargo-fuzz setup under fuzz/ with four targets:

Target Entry point
fuzz_lexer StrGraphQLTokenSource (full token iteration)
fuzz_parse_schema GraphQLParser::parse_schema_document()
fuzz_parse_executable GraphQLParser::parse_executable_document()
fuzz_parse_mixed GraphQLParser::parse_mixed_document()

Prerequisites

rustup toolchain install nightly
cargo install cargo-fuzz

Running Fuzz Tests

Quick smoke test (1 minute per target, parallel):

./crates/libgraphql-parser/scripts/run-fuzz-tests.sh

Sustained run (15 minutes per target, parallel):

./crates/libgraphql-parser/scripts/run-fuzz-tests.sh 15

Single target:

./crates/libgraphql-parser/scripts/run-fuzz-tests.sh 5 fuzz_lexer

Raw cargo fuzz (from the fuzz directory):

cd crates/libgraphql-parser/fuzz
cargo +nightly fuzz run fuzz_lexer -- -max_total_time=60

Latest Fuzz Testing Results

Date: 2026-02-10 Duration: 60 minutes per target (4 targets in parallel) Platform: macOS (aarch64), nightly Rust

Target Executions Exec/s Corpus Entries Crashes
fuzz_lexer 39,990,040 ~11,105 31,875 0
fuzz_parse_schema 9,220,187 ~2,560 43,160 0
fuzz_parse_executable 11,261,274 ~3,127 43,633 0
fuzz_parse_mixed 10,173,478 ~2,825 48,957 0

Total: 70,644,979 executions across all targets, zero crashes.

License

Licensed under the MIT license.