GitLab Grape OpenAPI
[!IMPORTANT] Internal use only. This gem exists to generate the OpenAPI 3.0 spec for the GitLab Rails monorepo and is not intended for use outside of GitLab.
- It is published to rubygems.org only so the monorepo's
Gemfilecan depend on it — not as a general-purpose Grape → OpenAPI tool.- The public API, configuration DSL, and generated output may change in any release, including patch releases. There is no semantic-versioning contract.
- No external support is provided. Issues and merge requests from outside GitLab may be closed without review.
- Feature work is driven by the needs of
gitlab-org/gitlab; capabilities that aren't needed there will not be added.
Internal gem for generating OpenAPI 3.0 specifications from Grape API definitions, used by gitlab-org/gitlab to publish its REST API reference.
Installation
Add to your Gemfile:
gem 'gitlab-grape-openapi'
Then run:
bundle install
Configuration
Configure the gem using the Gitlab::GrapeOpenapi.configure block, typically in an initializer:
Gitlab::GrapeOpenapi.configure do |config|
# Required: API metadata
config.info = Gitlab::GrapeOpenapi::Models::Info.new(
title: 'My API',
description: 'API description',
version: 'v1',
terms_of_service: 'https://example.com/terms'
)
# API path configuration
config.api_prefix = "api" # Default: "api"
config.api_version = "v1" # Default: "v1"
# Server definitions
config.servers = [
Gitlab::GrapeOpenapi::Models::Server.new(
url: 'https://{hostname}',
description: "Production API",
variables: {
hostname: Gitlab::GrapeOpenapi::Models::ServerVariable.new(
default: 'api.example.com',
description: 'API hostname'
)
}
)
]
# Security schemes
config.security_schemes = [
Gitlab::GrapeOpenapi::Models::SecurityScheme.new(
name: "bearerAuth",
type: "http",
scheme: "bearer"
)
]
# Exclude specific API classes from generation
config.excluded_api_classes = [
'API::Internal::Base',
'API::Internal::Admin'
]
# Override tag names for better display
config.tag_overrides = {
'Ci' => 'CI',
'Oauth' => 'OAuth'
}
# Map Grape route settings to OpenAPI extensions
config.annotations = {
lifecycle: 'x-gitlab-lifecycle'
}
end
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
info |
Models::Info |
nil |
API metadata (title, description, version, terms of service) |
api_prefix |
String |
"api" |
URL prefix for API routes |
api_version |
String |
"v1" |
API version string |
servers |
Array<Models::Server> |
[] |
Server definitions for the API |
security_schemes |
Array<Models::SecurityScheme> |
[] |
Authentication/authorization schemes |
excluded_api_classes |
Array<String> |
[] |
API class names to exclude from generation |
tag_overrides |
Hash |
{} |
Map of tag names to their display overrides |
annotations |
Hash |
{} |
Map of Grape route settings to OpenAPI extension names |
warnings |
Boolean |
false |
Emit stderr warnings for synthesized (undeclared) path params |
Annotations
The annotations configuration maps Grape route settings to OpenAPI vendor extensions. For example:
config.annotations = {
lifecycle: 'x-gitlab-lifecycle'
}
When a Grape endpoint has:
```ruby
route_setting :lifecycle, 'mature'
The generated OpenAPI spec will include:
x-gitlab-lifecycle: mature
Usage
Generating an OpenAPI Specification
# Load all API and entity classes
Rails.application.eager_load!
api_classes = API::Base.descendants
entity_classes = Grape::Entity.descendants
# Generate the specification
spec = Gitlab::GrapeOpenapi.generate(
api_classes: api_classes,
entity_classes: entity_classes
)
# Output as JSON
File.write('openapi.json', JSON.pretty_generate(spec))
# Or as YAML
require 'yaml'
File.write('openapi.yaml', spec.to_yaml)
Usage with gitlab-org/gitlab
- Start a Rails console in your GDK:
cd ~/gdk/gitlab
rails console
- Generate the OpenAPI specification:
Rails.application.eager_load!
api_classes = API::Base.descendants
entity_classes = Grape::Entity.descendants
spec = Gitlab::GrapeOpenapi.generate(api_classes: api_classes, entity_classes: entity_classes)
File.write(Rails.root.join('tmp', 'openapi.json'), JSON.pretty_generate(spec))
- The spec will be saved to
tmp/openapi.jsonin your GitLab directory.
Architecture
The gem follows a converter-based architecture:
Generator
├── TagConverter - Extracts tags from API classes
├── EntityConverter - Converts Grape::Entity to OpenAPI schemas
├── PathConverter - Converts routes to OpenAPI paths
│ ├── OperationConverter - Converts individual endpoints
│ ├── ParameterConverter - Converts endpoint parameters
│ ├── ResponseConverter - Converts endpoint responses
│ └── RequestBodyConverter - Converts request bodies
└── TypeResolver - Maps Ruby/Grape types to OpenAPI types
Registries
- SchemaRegistry - Tracks converted entity schemas
- RequestBodyRegistry - Tracks request body schemas
- TagRegistry - Tracks API tags
Development
bundle install
bundle exec rspec
Running Tests
bundle exec rspec
Linting
bundle exec rubocop
Releasing
Releases are driven by the
gem-release CI component
and a merge request whose title contains RELEASE.
- Open a merge request from the
Release template. The
/title RELEASE: v<NEW_VERSION>quick action ensures the title containsRELEASE, which is what exposes the release-creation andgem-publicationjobs. - Bump the
VERSIONconstant inlib/gitlab/grape_openapi/version.rband fill in the changelog per the template. - Get the MR reviewed and merged. Do not run the
gem-publicationjob from the MR pipeline — it is meant to stay un-run during review (see below). - After merge, follow the post-release steps in
CLAUDE.mdto bump thegitlab-grape-openapiversion ingitlab-org/gitlaband regenerate the committedopenapi_v3.yaml.
How publishing works
GEM_HOST_API_KEY is configured as an inherited, protected
variable on a parent group, so it is only exposed to pipelines running
on protected branches or protected tags. This is why running the
gem-publication job manually from the MR pipeline fails — the API key
is not available there.
The expected flow relies on the default branch being protected:
- During review, the MR-pipeline
gem-publicationjob stays un-run (it ismanual, and would fail anyway without the key). - When the version bump lands on the default branch (
main), thegem-publicationjob runs again — this time with the protectedGEM_HOST_API_KEYavailable — and publishes the gem to RubyGems.
The gem-release component's rules that re-trigger publication after merge:
.gem-release-default-rules:
rules:
- if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
changes: ["lib/**/version.rb"]
- if: '$CI_MERGE_REQUEST_TITLE =~ /RELEASE/ && "...dry_run..." == "true"'
- if: '$CI_MERGE_REQUEST_TITLE =~ /RELEASE/'
when: manual
The first rule is the one that publishes: a push to the default
branch that changes lib/**/version.rb.
Contributing
This gem is maintained by GitLab's API Platform team for internal use. External contributions are not actively solicited; issues and merge requests opened by non-GitLab contributors may be closed without review. GitLab team members should follow the standard contribution guidelines — see the project page.
License
Released under the MIT License.