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 exceedinggraphql_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 access —
valid()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 scale — 70M+
libfuzzerexecutions 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 viaAstNode::append_source(). graphql-parserv0.4 compatibility — bidirectional conversion betweenlibgraphql-parser's AST and thegraphql_parserv0.4 AST via thecompatmodule.
Coming soon:
- Drop-in compat with
apollo-parser— translation utils to make it easy to integrate with tools that already depend on theapollo_parser::cstAST structure.
Getting Started
Or add this to your Cargo.toml:
[]
= "0.0.4"
Usage
Parse a GraphQL schema document:
use GraphQLParser;
let result = new
.parse_schema_document;
assert!;
let = result.valid.unwrap;
Parse an executable document (queries, mutations, subscriptions):
use GraphQLParser;
let result = new
.parse_executable_document;
assert!;
Get the line and column of a node:
use GraphQLParser;
let result = new
.parse_schema_document;
let = result.valid.unwrap;
let query_def_node = &doc.definitions;
let source_span = query_def_node.source_span.unwrap;
// line_col() returns 0-based (line, column) pairs where columns
// are measured in UTF-8 characters (not bytes or UTF-16 units).
let = 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 GraphQLParser;
// This schema has errors: missing `:` on field type, unclosed brace
let source = "type Query { hello String";
let result = new.parse_schema_document;
// Errors are collected — inspect them all at once
assert!;
for error in result.errors
// 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!;
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 GraphQLParser;
let source = "type Query { hello: String }";
let result = new.parse_schema_document;
// Strict mode: returns AST only if there were zero errors.
// Use this when compiling schemas or executing queries.
if let Some = result.valid
// 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.,SmallVecfor 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),
--releaseprofile. Comparison parsers:graphql-parser0.4.1,apollo-parser0.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
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
Running Fuzz Tests
Quick smoke test (1 minute per target, parallel):
Sustained run (15 minutes per target, parallel):
Single target:
Raw cargo fuzz (from the fuzz directory):
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.