Introducing graphql-lint and gqlint
Today we're introducing a new open-source Rust based tool for the GraphQL ecosystem -
When we started implementing our platform linter for GraphQL schemas, we noticed that in contrast to the JavaScript and TypeScript ecosystems (where many new tools are now written in Rust and greatly accelerate the developer feedback loop and CI runtimes), there currently doesn't exist a widely adopted Rust-native linter for GraphQL.
To this end we've decided to open-source and offer as a separate crate our GraphQL linting solution, graphql-lint
and its CLI - gqlint
.
We hope that by extending and allowing free using of this foundation the GraphQL ecosystem as a whole will benefit.
graphql-lint
can lint an SDL schema in a few microseconds (25.953µs
for example, for our test schema benchmark)
and would fit perfectly into scenarios like a language server in the future for instant in-editor feedback. For a rough comparison,
we benchmarked running a JS based linter (with a comparable configuration) in a relatively fast JS runtime at 559.7ms
. That means
you could lint an SDL schema 21,565 times in the time it took to run the JS based linter, or in other words, a 21,565X performance improvement.
For larger schemas (2533 LOC) we saw 454.79µs
vs 698.4ms
, a 1,535X improvement.
For parsing the schema and producing an AST graphql-lint
uses the great cynic-parser
, make sure to check it out as well.
Our first iteration of graphql-lint
supports the following:
- Naming conventions
- Types:
PascalCase
- Forbidden prefixes:
"Type"
- Forbidden suffixes:
"Type"
- Forbidden prefixes:
- Fields:
camelCase
- Input values:
camelCase
- Arguments:
camelCase
- Directives:
camelCase
- Enums:
PascalCase
- Forbidden prefixes:
"Enum"
- Forbidden suffixes:
"Enum"
- Forbidden prefixes:
- Unions
- Forbidden prefixes:
"Union"
- Forbidden suffixes:
"Union"
- Forbidden prefixes:
- Enum values:
SCREAMING_SNAKE_CASE
- Interfaces
- Forbidden prefixes:
"Interface"
- Forbidden suffixes:
"Interface"
- Forbidden prefixes:
- Query fields
- Forbidden prefixes:
["query", "get", "list"]
- Forbidden suffixes:
"Query"
- Forbidden prefixes:
- Mutation fields
- Forbidden prefixes:
["mutation", "put", "post", "patch"]
- Forbidden suffixes:
"Mutation"
- Forbidden prefixes:
- Subscription fields
- Forbidden prefixes:
"subscription"
- Forbidden suffixes:
"Subscription"
- Forbidden prefixes:
- Types:
- Usage of the
@deprecated
directive requires specifying thereason
argument
Future versions will bring with them additional rules and the ability to configure the linter.
To add graphql-lint
to your project, add the following to your Cargo.toml
:
[dependencies]
graphql-lint = "0.1.3"
Or run cargo add graphql-lint
in your project, and make use of the exported lint
function as follows:
use graphql_lint::lint;
fn main () {
let schema = r#"
type Query {
hello: String!
}
"#;
let violations = lint(schema).unwrap();
}
This usage will change as we add new features and configurability.
gqlint
is a CLI that runs graphql-lint
for command line use.
You can currently install it using cargo
by running:
cargo install gqlint
And use it as follows:
$ gqlint schema.graphql
⚠️ [Warning]: directive 'WithDeprecatedArgs' should be renamed to 'withDeprecatedArgs'
⚠️ [Warning]: argument 'ARG' on directive 'WithDeprecatedArgs' should be renamed to 'arg'
⚠️ [Warning]: enum 'Enum_lowercase' should be renamed to 'EnumLowercase'
⚠️ [Warning]: enum 'Enum_lowercase' has a forbidden prefix: 'Enum'
⚠️ [Warning]: usage of directive 'deprecated' on enum 'Enum_lowercase' does not populate the 'reason' argument
⚠️ [Warning]: value 'an_enum_member' on enum 'Enum_lowercase' should be renamed to 'AN_ENUM_MEMBER'
⚠️ [Warning]: usage of directive 'deprecated' on enum value 'an_enum_member' on enum 'Enum_lowercase' does not populate the 'reason' argument
⚠️ [Warning]: enum 'lowercase_Enum' should be renamed to 'LowercaseEnum'
⚠️ [Warning]: enum 'lowercase_Enum' has a forbidden suffix: 'Enum'
⚠️ [Warning]: value 'an_enum_member' on enum 'lowercase_Enum' should be renamed to 'AN_ENUM_MEMBER'
⚠️ [Warning]: usage of directive 'deprecated' on enum value 'an_enum_member' on enum 'lowercase_Enum' does not populate the 'reason' argument
⚠️ [Warning]: field 'getHello' on type 'Query' has a forbidden prefix: 'get'
⚠️ [Warning]: field 'queryHello' on type 'Query' has a forbidden prefix: 'query'
⚠️ [Warning]: field 'listHello' on type 'Query' has a forbidden prefix: 'list'
⚠️ [Warning]: field 'helloQuery' on type 'Query' has a forbidden suffix: 'Query'
⚠️ [Warning]: field 'putHello' on type 'Mutation' has a forbidden prefix: 'put'
⚠️ [Warning]: field 'mutationHello' on type 'Mutation' has a forbidden prefix: 'mutation'
⚠️ [Warning]: field 'postHello' on type 'Mutation' has a forbidden prefix: 'post'
⚠️ [Warning]: field 'patchHello' on type 'Mutation' has a forbidden prefix: 'patch'
⚠️ [Warning]: field 'helloMutation' on type 'Mutation' has a forbidden suffix: 'Mutation'
⚠️ [Warning]: field 'subscriptionHello' on type 'Subscription' has a forbidden prefix: 'subscription'
⚠️ [Warning]: field 'helloSubscription' on type 'Subscription' has a forbidden suffix: 'Subscription'
⚠️ [Warning]: type 'TypeTest' has a forbidden prefix: 'Type'
⚠️ [Warning]: usage of directive 'deprecated' on field 'name' on type 'TypeTest' does not populate the 'reason' argument
⚠️ [Warning]: type 'TestType' has a forbidden suffix: 'Type'
⚠️ [Warning]: type 'other' should be renamed to 'Other'
⚠️ [Warning]: usage of directive 'deprecated' on scalar 'CustomScalar' does not populate the 'reason' argument
⚠️ [Warning]: union 'UnionTest' has a forbidden prefix: 'Union'
⚠️ [Warning]: usage of directive 'deprecated' on union 'UnionTest' does not populate the 'reason' argument
⚠️ [Warning]: union 'TestUnion' has a forbidden suffix: 'Union'
⚠️ [Warning]: interface 'GameInterface' has a forbidden suffix: 'Interface'
⚠️ [Warning]: usage of directive 'deprecated' on field 'publisher' on interface 'GameInterface' does not populate the 'reason' argument
⚠️ [Warning]: interface 'InterfaceGame' has a forbidden prefix: 'Interface'
⚠️ [Warning]: usage of directive 'deprecated' on interface 'InterfaceGame' does not populate the 'reason' argument
⚠️ [Warning]: usage of directive 'deprecated' on input 'TEST' does not populate the 'reason' argument
⚠️ [Warning]: input value 'OTHER' on input 'TEST' should be renamed to 'other'
⚠️ [Warning]: usage of directive 'deprecated' on input value 'OTHER' on input 'TEST' does not populate the 'reason' argument
⚠️ [Warning]: type 'hello' should be renamed to 'Hello'
⚠️ [Warning]: usage of directive 'deprecated' on type 'hello' does not populate the 'reason' argument
⚠️ [Warning]: field 'Test' on type 'hello' should be renamed to 'test'
⚠️ [Warning]: argument 'NAME' on field 'Test' on type 'hello' should be renamed to 'name'
⚠️ [Warning]: type 'hello' should be renamed to 'Hello'
⚠️ [Warning]: field 'GOODBYE' on type 'hello' should be renamed to 'goodbye'