summaryrefslogtreecommitdiff
path: root/doc/development/api_graphql_styleguide.md
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development/api_graphql_styleguide.md')
-rw-r--r--doc/development/api_graphql_styleguide.md94
1 files changed, 64 insertions, 30 deletions
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index c12b66a94a7..40cc8f5ec45 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -11,9 +11,12 @@ This document outlines the style guide for the GitLab [GraphQL API](../api/graph
## How GitLab implements GraphQL
<!-- vale gitlab.Spelling = NO -->
+
We use the [GraphQL Ruby gem](https://graphql-ruby.org/) written by [Robert Mosolgo](https://github.com/rmosolgo/).
+In addition, we have a subscription to [GraphQL Pro](https://graphql.pro/). For
+details see [GraphQL Pro subscription](graphql_guide/graphql_pro.md).
+
<!-- vale gitlab.Spelling = YES -->
-In addition, we have a subscription to [GraphQL Pro](https://graphql.pro/). For details see [GraphQL Pro subscription](graphql_guide/graphql_pro.md).
All GraphQL queries are directed to a single endpoint
([`app/controllers/graphql_controller.rb#execute`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app%2Fcontrollers%2Fgraphql_controller.rb)),
@@ -133,20 +136,24 @@ For example, `app/graphql/types/issue_type.rb`:
```ruby
graphql_name 'Issue'
-field :iid, GraphQL::ID_TYPE, null: true
-field :title, GraphQL::STRING_TYPE, null: true
+field :iid, GraphQL::Types::ID, null: true
+field :title, GraphQL::Types::String, null: true
# we also have a method here that we've defined, that extends `field`
markdown_field :title_html, null: true
-field :description, GraphQL::STRING_TYPE, null: true
+field :description, GraphQL::Types::String, null: true
markdown_field :description_html, null: true
```
We give each type a name (in this case `Issue`).
The `iid`, `title` and `description` are _scalar_ GraphQL types.
-`iid` is a `GraphQL::ID_TYPE`, a special string type that signifies a unique ID.
-`title` and `description` are regular `GraphQL::STRING_TYPE` types.
+`iid` is a `GraphQL::Types::ID`, a special string type that signifies a unique ID.
+`title` and `description` are regular `GraphQL::Types::String` types.
+
+Note that the old scalar types `GraphQL:ID`, `GraphQL::INT_TYPE`, `GraphQL::STRING_TYPE`,
+and `GraphQL:BOOLEAN_TYPE` are no longer allowed. Please use `GraphQL::Types::ID`,
+`GraphQL::Types::Int`, `GraphQL::Types::String`, and `GraphQL::Types::Boolean`.
When exposing a model through the GraphQL API, we do so by creating a
new type in `app/graphql/types`. You can also declare custom GraphQL data types
@@ -225,7 +232,7 @@ Using an example from
[`Types::Notes::DiscussionType`](https://gitlab.com/gitlab-org/gitlab/-/blob/3c95bd9/app/graphql/types/notes/discussion_type.rb#L24-26):
```ruby
-field :reply_id, GraphQL::ID_TYPE
+field :reply_id, GraphQL::Types::ID
def reply_id
::Gitlab::GlobalId.build(object, id: object.reply_id)
@@ -402,7 +409,7 @@ class BranchResolver < BaseResolver
type ::Types::BranchType, null: true
calls_gitaly!
- argument name: ::GraphQL::STRING_TYPE, required: true
+ argument name: ::GraphQL::Types::String, required: true
def resolve(name:)
object.branch(name)
@@ -499,7 +506,7 @@ everyone.
Example:
```ruby
-field :test_field, type: GraphQL::STRING_TYPE,
+field :test_field, type: GraphQL::Types::String,
null: true,
description: 'Some test field.',
feature_flag: :my_feature_flag
@@ -523,7 +530,7 @@ When applying a feature flag to toggle the value of a field, the
Example:
```ruby
-field :foo, GraphQL::STRING_TYPE,
+field :foo, GraphQL::Types::String,
null: true,
description: 'Some test field. Will always return `null`' \
'if `my_feature_flag` feature flag is disabled.'
@@ -553,7 +560,7 @@ The value of the property is a `Hash` of:
Example:
```ruby
-field :token, GraphQL::STRING_TYPE, null: true,
+field :token, GraphQL::Types::String, null: true,
deprecated: { reason: 'Login via token has been removed', milestone: '10.0' },
description: 'Token for login.'
```
@@ -795,15 +802,15 @@ We can use GraphQL types like this:
```ruby
module Types
class ChartType < BaseObject
- field :title, GraphQL::STRING_TYPE, null: true, description: 'Title of the chart.'
+ field :title, GraphQL::Types::String, null: true, description: 'Title of the chart.'
field :data, [Types::ChartDatumType], null: true, description: 'Data of the chart.'
end
end
module Types
class ChartDatumType < BaseObject
- field :x, GraphQL::INT_TYPE, null: true, description: 'X-axis value of the chart datum.'
- field :y, GraphQL::INT_TYPE, null: true, description: 'Y-axis value of the chart datum.'
+ field :x, GraphQL::Types::Int, null: true, description: 'X-axis value of the chart datum.'
+ field :y, GraphQL::Types::Int, null: true, description: 'Y-axis value of the chart datum.'
end
end
```
@@ -817,7 +824,7 @@ A description of a field or argument is given using the `description:`
keyword. For example:
```ruby
-field :id, GraphQL::ID_TYPE, description: 'ID of the resource.'
+field :id, GraphQL::Types::ID, description: 'ID of the resource.'
```
Descriptions of fields and arguments are viewable to users through:
@@ -833,8 +840,8 @@ descriptions:
- Mention the name of the resource in the description. Example:
`'Labels of the issue'` (issue being the resource).
- Use `"{x} of the {y}"` where possible. Example: `'Title of the issue'`.
- Do not start descriptions with `The`.
-- Descriptions of `GraphQL::BOOLEAN_TYPE` fields should answer the question: "What does
+ Do not start descriptions with `The` or `A`, for consistency and conciseness.
+- Descriptions of `GraphQL::Types::Boolean` fields should answer the question: "What does
this field do?". Example: `'Indicates project has a Git repository'`.
- Always include the word `"timestamp"` when describing an argument or
field of type `Types::TimeType`. This lets the reader know that the
@@ -844,8 +851,8 @@ descriptions:
Example:
```ruby
-field :id, GraphQL::ID_TYPE, description: 'ID of the issue.'
-field :confidential, GraphQL::BOOLEAN_TYPE, description: 'Indicates the issue is confidential.'
+field :id, GraphQL::Types::ID, description: 'ID of the issue.'
+field :confidential, GraphQL::Types::Boolean, description: 'Indicates the issue is confidential.'
field :closed_at, Types::TimeType, description: 'Timestamp of when the issue was closed.'
```
@@ -861,7 +868,7 @@ passing it the type, and field name to copy the description of.
Example:
```ruby
-argument :title, GraphQL::STRING_TYPE,
+argument :title, GraphQL::Types::String,
required: false,
description: copy_field_description(Types::MergeRequestType, :title)
```
@@ -874,7 +881,7 @@ provide a `see` property on fields. For example:
```ruby
field :genus,
- type: GraphQL::STRING_TYPE,
+ type: GraphQL::Types::String,
null: true,
description: 'A taxonomic genus.'
see: { 'Wikipedia page on genera' => 'https://wikipedia.org/wiki/Genus' }
@@ -923,7 +930,7 @@ class PostResolver < BaseResolver
authorize :read_blog
description 'Blog posts, optionally filtered by name'
- argument :name, [::GraphQL::STRING_TYPE], required: false, as: :slug
+ argument :name, [::GraphQL::Types::String], required: false, as: :slug
alias_method :blog, :object
@@ -1015,10 +1022,10 @@ class JobsResolver < BaseResolver
type JobType.connection_type, null: true
authorize :read_pipeline
- argument :name, [::GraphQL::STRING_TYPE], required: false
+ argument :name, [::GraphQL::Types::String], required: false
when_single do
- argument :name, ::GraphQL::STRING_TYPE, required: true
+ argument :name, ::GraphQL::Types::String, required: true
end
def resolve(**args)
@@ -1039,13 +1046,13 @@ class JobsResolver < BaseResolver
type JobType.connection_type, null: true
authorize :read_pipeline
- argument :name, [::GraphQL::STRING_TYPE], required: false
+ argument :name, [::GraphQL::Types::String], required: false
argument :id, [::Types::GlobalIDType[::Job]],
required: false,
prepare: ->(ids, ctx) { ids.map(&:model_id) }
when_single do
- argument :name, ::GraphQL::STRING_TYPE, required: false
+ argument :name, ::GraphQL::Types::String, required: false
argument :id, ::Types::GlobalIDType[::Job],
required: false
prepare: ->(id, ctx) { id.model_id }
@@ -1357,11 +1364,36 @@ Arguments for a mutation are defined using `argument`.
Example:
```ruby
-argument :my_arg, GraphQL::STRING_TYPE,
+argument :my_arg, GraphQL::Types::String,
required: true,
description: "A description of the argument."
```
+#### Nullability
+
+Arguments can be marked as `required: true` which means the value must be present and not `null`.
+If a required argument's value can be `null`, use the `required: :nullable` declaration.
+
+Example:
+
+```ruby
+argument :due_date,
+ Types::TimeType,
+ required: :nullable,
+ description: 'The desired due date for the issue. Due date is removed if null.'
+```
+
+In the above example, the `due_date` argument must be given, but unlike the GraphQL spec, the value can be `null`.
+This allows 'unsetting' the due date in a single mutation rather than creating a new mutation for removing the due date.
+
+```ruby
+{ due_date: null } # => OK
+{ due_date: "2025-01-10" } # => OK
+{ } # => invalid (not given)
+```
+
+#### Keywords
+
Each GraphQL `argument` defined is passed to the `#resolve` method
of a mutation as keyword arguments.
@@ -1373,6 +1405,8 @@ def resolve(my_arg:)
end
```
+#### Input Types
+
`graphql-ruby` wraps up arguments into an
[input type](https://graphql.org/learn/schema/#input-types).
@@ -1382,16 +1416,16 @@ defines these arguments (some
[through inheritance](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/mutations/merge_requests/base.rb)):
```ruby
-argument :project_path, GraphQL::ID_TYPE,
+argument :project_path, GraphQL::Types::ID,
required: true,
description: "The project the merge request to mutate is in."
-argument :iid, GraphQL::STRING_TYPE,
+argument :iid, GraphQL::Types::String,
required: true,
description: "The IID of the merge request to mutate."
argument :draft,
- GraphQL::BOOLEAN_TYPE,
+ GraphQL::Types::Boolean,
required: false,
description: <<~DESC
Whether or not to set the merge request as a draft.