libgraphql-parser 0.0.5

A blazing fast, error-focused, lossless GraphQL parser for schema, executable, and mixed documents.
Documentation
<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jeffmo/libgraphql/refs/heads/main/crates/libgraphql-parser/assets/readme-banner-dark.svg" />
    <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/jeffmo/libgraphql/refs/heads/main/crates/libgraphql-parser/assets/readme-banner-light.svg" />
    <img src="https://raw.githubusercontent.com/jeffmo/libgraphql/refs/heads/main/crates/libgraphql-parser/assets/readme-banner-light.svg" alt="libgraphql-parser — Blazing fast, error-resilient GraphQL parser" width="100%" />
  </picture>
</p>

<p align="center">
  <a href="https://crates.io/crates/libgraphql-parser"><img src="https://img.shields.io/crates/v/libgraphql-parser.svg?style=flat-square" alt="crates.io" /></a>
  <a href="https://docs.rs/libgraphql-parser/"><img src="https://img.shields.io/docsrs/libgraphql-parser?style=flat-square" alt="docs.rs" /></a>
  <a href="https://github.com/jeffmo/libgraphql/blob/main/LICENSE"><img src="https://img.shields.io/crates/l/libgraphql-parser.svg?style=flat-square" alt="license" /></a>
  <img src="https://img.shields.io/badge/GraphQL_Spec-September_2025-e535ab?style=flat-square" alt="GraphQL Spec" />
</p>

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jeffmo/libgraphql/refs/heads/main/crates/libgraphql-parser/assets/readme-code-dark.svg" />
  <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/jeffmo/libgraphql/refs/heads/main/crates/libgraphql-parser/assets/readme-code-light.svg" />
  <img src="https://raw.githubusercontent.com/jeffmo/libgraphql/refs/heads/main/crates/libgraphql-parser/assets/readme-code-light.svg" alt="Two-column code preview showing GraphQL schema and query parsing with syntax-highlighted error diagnostics" width="100%" />
</picture>

<br />
<br />

## 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]#performance exceeding
  [`apollo_parser`]https://crates.io/crates/apollo-parser (v0.8.4) and 
  matching or exceeding 
  [`graphql_parser`]https://crates.io/crates/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]https://spec.graphql.org/September2025/ 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+ `libfuzzer` executions]#fuzz-testing 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`]https://crates.io/crates/graphql-parser v0.4 AST via the
  [`compat`]https://docs.rs/libgraphql-parser/latest/libgraphql_parser/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`]https://docs.rs/apollo-parser/0.8.4/apollo_parser/cst/index.html
  AST structure.

## Getting Started

```bash
cargo add libgraphql-parser
```

Or add this to your `Cargo.toml`:
```toml
[dependencies]
libgraphql-parser = "0.0.4"
```

## Usage

Parse a GraphQL schema document:

```rust
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):

```rust
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:

```rust
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:

```rust
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:

```text
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:

```text
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:

```rust
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]https://spec.graphql.org/September2025/ 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](https://github.com/bheisler/criterion.rs)
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.).                                                          |

[`GraphQLParser<S>`]: https://docs.rs/libgraphql-parser/latest/libgraphql_parser/struct.GraphQLParser.html
[`ParseResult<T>`]: https://docs.rs/libgraphql-parser/latest/libgraphql_parser/enum.ParseResult.html
[`StrGraphQLTokenSource`]: https://docs.rs/libgraphql-parser/latest/libgraphql_parser/token/struct.StrGraphQLTokenSource.html
[`GraphQLParseError`]: https://docs.rs/libgraphql-parser/latest/libgraphql_parser/struct.GraphQLParseError.html
[`GraphQLTokenSource`]: https://docs.rs/libgraphql-parser/latest/libgraphql_parser/token/trait.GraphQLTokenSource.html

## Part of the `libgraphql` Ecosystem

`libgraphql-parser` is the parsing foundation of the
[`libgraphql`](https://crates.io/crates/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

```bash
cargo test --package libgraphql-parser
```

## Fuzz Testing

The crate includes a [`cargo-fuzz`](https://github.com/rust-fuzz/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

```bash
rustup toolchain install nightly
cargo install cargo-fuzz
```

### Running Fuzz Tests

**Quick smoke test (1 minute per target, parallel):**
```bash
./crates/libgraphql-parser/scripts/run-fuzz-tests.sh
```

**Sustained run (15 minutes per target, parallel):**
```bash
./crates/libgraphql-parser/scripts/run-fuzz-tests.sh 15
```

**Single target:**
```bash
./crates/libgraphql-parser/scripts/run-fuzz-tests.sh 5 fuzz_lexer
```

**Raw `cargo fuzz` (from the fuzz directory):**
```bash
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](https://github.com/jeffmo/libgraphql/blob/main/LICENSE).