From 10bf39632af67a5e2e9f04f071f11b42ae09b03e Mon Sep 17 00:00:00 2001
From: chris48s <chris48s@users.noreply.github.com>
Date: Sat, 30 Dec 2023 17:38:31 +0000
Subject: [PATCH] migrate examples to openApi part 26; affects [gitlab] (#9752)

* replace failing 'other' test with a mock

* update pipeline/coverage 'not found' tests

* migrate gitlab from examples to openApi

* eat own tail
---
 .../gitlab/gitlab-contributors.service.js     |  36 +--
 services/gitlab/gitlab-forks.service.js       |  32 +-
 services/gitlab/gitlab-helper.js              |   4 +-
 services/gitlab/gitlab-issues.service.js      | 218 +++-----------
 .../gitlab/gitlab-languages-count.service.js  |  31 +-
 services/gitlab/gitlab-last-commit.service.js |  34 ++-
 services/gitlab/gitlab-license.service.js     |  44 ++-
 services/gitlab/gitlab-license.tester.js      |  21 +-
 .../gitlab/gitlab-merge-requests.service.js   | 284 +++---------------
 .../gitlab-pipeline-coverage.service.js       |  65 ++--
 .../gitlab/gitlab-pipeline-coverage.tester.js |  10 +-
 .../gitlab/gitlab-pipeline-status.service.js  |  46 +--
 .../gitlab/gitlab-pipeline-status.tester.js   |   7 +-
 services/gitlab/gitlab-release.service.js     |   4 +-
 services/gitlab/gitlab-stars.service.js       |  32 +-
 services/gitlab/gitlab-tag.service.js         |  77 ++---
 16 files changed, 313 insertions(+), 632 deletions(-)

diff --git a/services/gitlab/gitlab-contributors.service.js b/services/gitlab/gitlab-contributors.service.js
index 76415edf46..d9fdc2d2cf 100644
--- a/services/gitlab/gitlab-contributors.service.js
+++ b/services/gitlab/gitlab-contributors.service.js
@@ -1,7 +1,8 @@
 import Joi from 'joi'
+import { pathParam, queryParam } from '../index.js'
 import { optionalUrl, nonNegativeInteger } from '../validators.js'
 import { renderContributorBadge } from '../contributor-count.js'
-import { documentation, httpErrorsFor } from './gitlab-helper.js'
+import { description, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.object({ 'x-total': nonNegativeInteger }).required()
@@ -18,25 +19,24 @@ export default class GitlabContributors extends GitLabBase {
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'GitLab contributors',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
+  static openApi = {
+    '/gitlab/contributors/{project}': {
+      get: {
+        summary: 'GitLab Contributors',
+        description,
+        parameters: [
+          pathParam({
+            name: 'project',
+            example: 'gitlab-org/gitlab',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+        ],
       },
-      staticPreview: this.render({ contributorCount: 418 }),
-      documentation,
     },
-    {
-      title: 'GitLab (self-managed) contributors',
-      queryParams: { gitlab_url: 'https://jihulab.com' },
-      namedParams: {
-        project: 'gitlab-cn/gitlab',
-      },
-      staticPreview: this.render({ contributorCount: 415 }),
-      documentation,
-    },
-  ]
+  }
 
   static defaultBadgeData = { label: 'contributors' }
 
diff --git a/services/gitlab/gitlab-forks.service.js b/services/gitlab/gitlab-forks.service.js
index d06b0bb7c0..8034b9757b 100644
--- a/services/gitlab/gitlab-forks.service.js
+++ b/services/gitlab/gitlab-forks.service.js
@@ -1,8 +1,9 @@
 import Joi from 'joi'
+import { pathParam, queryParam } from '../index.js'
 import { optionalUrl, nonNegativeInteger } from '../validators.js'
 import { metric } from '../text-formatters.js'
 import GitLabBase from './gitlab-base.js'
-import { documentation } from './gitlab-helper.js'
+import { description } from './gitlab-helper.js'
 
 const schema = Joi.object({
   forks_count: nonNegativeInteger,
@@ -21,21 +22,24 @@ export default class GitlabForks extends GitLabBase {
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'GitLab forks',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
+  static openApi = {
+    '/gitlab/forks/{project}': {
+      get: {
+        summary: 'GitLab Forks',
+        description,
+        parameters: [
+          pathParam({
+            name: 'project',
+            example: 'gitlab-org/gitlab',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+        ],
       },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'Fork',
-        message: '6.4k',
-        style: 'social',
-      },
-      documentation,
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'forks', namedLogo: 'gitlab' }
 
diff --git a/services/gitlab/gitlab-helper.js b/services/gitlab/gitlab-helper.js
index 1f2a5947aa..389ecc4cbd 100644
--- a/services/gitlab/gitlab-helper.js
+++ b/services/gitlab/gitlab-helper.js
@@ -1,4 +1,4 @@
-const documentation = `
+const description = `
 You may use your GitLab Project Id (e.g. 278964) or your Project Path (e.g.
 [gitlab-org/gitlab](https://gitlab.com/gitlab-org/gitlab) ).
 Note that only internet-accessible GitLab instances are supported, for example
@@ -14,4 +14,4 @@ function httpErrorsFor(notFoundMessage = 'project not found') {
   }
 }
 
-export { documentation, httpErrorsFor }
+export { description, httpErrorsFor }
diff --git a/services/gitlab/gitlab-issues.service.js b/services/gitlab/gitlab-issues.service.js
index d3488a3b50..599cc718a8 100644
--- a/services/gitlab/gitlab-issues.service.js
+++ b/services/gitlab/gitlab-issues.service.js
@@ -1,7 +1,8 @@
 import Joi from 'joi'
+import { pathParam, queryParam } from '../index.js'
 import { optionalUrl, nonNegativeInteger } from '../validators.js'
 import { metric } from '../text-formatters.js'
-import { documentation, httpErrorsFor } from './gitlab-helper.js'
+import { description, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.object({
@@ -19,192 +20,44 @@ const queryParamSchema = Joi.object({
   gitlab_url: optionalUrl,
 }).required()
 
-const labelDocumentation = `
-<p>
-  If you want to use multiple labels then please use commas (<code>,</code>) to separate them, e.g. <code>foo,bar</code>.
-</p>
-`
-
 export default class GitlabIssues extends GitLabBase {
   static category = 'issue-tracking'
 
   static route = {
     base: 'gitlab/issues',
-    pattern: ':variant(all|open|closed):raw(-raw)?/:project+',
+    pattern: ':variant(all|all-raw|open|open-raw|closed|closed-raw)/:project+',
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'GitLab issues',
-      pattern: 'open/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'issues',
-        message: '44k open',
-        color: 'yellow',
-      },
-      documentation,
-    },
-    {
-      title: 'GitLab issues',
-      pattern: 'open-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'open issues',
-        message: '44k',
-        color: 'yellow',
-      },
-      documentation,
-    },
-    {
-      title: 'GitLab issues by-label',
-      pattern: 'open/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,failure::new',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'test,failure::new issues',
-        message: '16 open',
-        color: 'yellow',
-      },
-      documentation: documentation + labelDocumentation,
-    },
-    {
-      title: 'GitLab issues by-label',
-      pattern: 'open-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,failure::new',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'open test,failure::new issues',
-        message: '16',
-        color: 'yellow',
-      },
-      documentation: documentation + labelDocumentation,
-    },
-    {
-      title: 'GitLab closed issues',
-      pattern: 'closed/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'issues',
-        message: '72k closed',
-        color: 'yellow',
-      },
-      documentation,
-    },
-    {
-      title: 'GitLab closed issues',
-      pattern: 'closed-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'closed issues',
-        message: '72k ',
-        color: 'yellow',
+  static openApi = {
+    '/gitlab/issues/{variant}/{project}': {
+      get: {
+        summary: 'GitLab Issues',
+        description,
+        parameters: [
+          pathParam({
+            name: 'variant',
+            example: 'all',
+            schema: { type: 'string', enum: this.getEnum('variant') },
+          }),
+          pathParam({
+            name: 'project',
+            example: 'gitlab-org/gitlab',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+          queryParam({
+            name: 'labels',
+            example: 'test,failure::new',
+            description:
+              'If you want to use multiple labels, you can use a comma (<code>,</code>) to separate them, e.g. <code>foo,bar</code>',
+          }),
+        ],
       },
-      documentation,
     },
-    {
-      title: 'GitLab closed issues by-label',
-      pattern: 'closed/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,failure::new',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'test,failure::new issues',
-        message: '4 closed',
-        color: 'yellow',
-      },
-      documentation: documentation + labelDocumentation,
-    },
-    {
-      title: 'GitLab closed issues by-label',
-      pattern: 'closed-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,failure::new',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'closed test,failure::new issues',
-        message: '4',
-        color: 'yellow',
-      },
-      documentation: documentation + labelDocumentation,
-    },
-    {
-      title: 'GitLab all issues',
-      pattern: 'all/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'issues',
-        message: '115k all',
-        color: 'yellow',
-      },
-      documentation,
-    },
-    {
-      title: 'GitLab all issues',
-      pattern: 'all-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'all issues',
-        message: '115k',
-        color: 'yellow',
-      },
-      documentation,
-    },
-    {
-      title: 'GitLab all issues by-label',
-      pattern: 'all-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,failure::new',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'all test,failure::new issues',
-        message: '20',
-        color: 'yellow',
-      },
-      documentation: documentation + labelDocumentation,
-    },
-  ]
+  }
 
   static defaultBadgeData = { label: 'issues', color: 'informational' }
 
@@ -215,7 +68,7 @@ export default class GitlabIssues extends GitLabBase {
 
     let labelPrefix = ''
     let messageSuffix = ''
-    if (raw !== undefined) {
+    if (raw) {
       labelPrefix = `${state} `
     } else {
       messageSuffix = state
@@ -246,12 +99,15 @@ export default class GitlabIssues extends GitLabBase {
     let issueCount
     switch (state) {
       case 'open':
+      case 'open-raw':
         issueCount = statistics.counts.opened
         break
       case 'closed':
+      case 'closed-raw':
         issueCount = statistics.counts.closed
         break
       case 'all':
+      case 'all-raw':
         issueCount = statistics.counts.all
         break
     }
@@ -260,7 +116,7 @@ export default class GitlabIssues extends GitLabBase {
   }
 
   async handle(
-    { variant, raw, project },
+    { variant, project },
     { gitlab_url: baseUrl = 'https://gitlab.com', labels },
   ) {
     const { statistics } = await this.fetch({
@@ -269,8 +125,8 @@ export default class GitlabIssues extends GitLabBase {
       labels,
     })
     return this.constructor.render({
-      variant,
-      raw,
+      variant: variant.split('-')[0],
+      raw: variant.endsWith('-raw'),
       labels,
       issueCount: this.constructor.transform({ variant, statistics }),
     })
diff --git a/services/gitlab/gitlab-languages-count.service.js b/services/gitlab/gitlab-languages-count.service.js
index 686003f252..faeb33995b 100644
--- a/services/gitlab/gitlab-languages-count.service.js
+++ b/services/gitlab/gitlab-languages-count.service.js
@@ -1,7 +1,8 @@
 import Joi from 'joi'
+import { pathParam, queryParam } from '../index.js'
 import { optionalUrl } from '../validators.js'
 import { metric } from '../text-formatters.js'
-import { documentation, httpErrorsFor } from './gitlab-helper.js'
+import { description, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 /*
@@ -23,20 +24,24 @@ export default class GitlabLanguageCount extends GitLabBase {
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'GitLab language count',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
+  static openApi = {
+    '/gitlab/languages/count/{project}': {
+      get: {
+        summary: 'GitLab Language Count',
+        description,
+        parameters: [
+          pathParam({
+            name: 'project',
+            example: 'gitlab-org/gitlab',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+        ],
       },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'languages',
-        message: '5',
-      },
-      documentation,
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'languages' }
 
diff --git a/services/gitlab/gitlab-last-commit.service.js b/services/gitlab/gitlab-last-commit.service.js
index 72bd745a3f..d08875caac 100644
--- a/services/gitlab/gitlab-last-commit.service.js
+++ b/services/gitlab/gitlab-last-commit.service.js
@@ -1,8 +1,9 @@
 import Joi from 'joi'
+import { pathParam, queryParam } from '../index.js'
 import { optionalUrl } from '../validators.js'
 import { formatDate } from '../text-formatters.js'
 import { age as ageColor } from '../color-formatters.js'
-import { documentation, httpErrorsFor } from './gitlab-helper.js'
+import { description, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.array()
@@ -25,7 +26,7 @@ const refText = `
 </p>
 `
 
-const defaultDocumentation = documentation + refText
+const lastCommitDescription = description + refText
 
 export default class GitlabLastCommit extends GitLabBase {
   static category = 'activity'
@@ -36,17 +37,28 @@ export default class GitlabLastCommit extends GitLabBase {
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'GitLab last commit',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
+  static openApi = {
+    '/gitlab/last-commit/{project}': {
+      get: {
+        summary: 'GitLab Last Commit',
+        description: lastCommitDescription,
+        parameters: [
+          pathParam({
+            name: 'project',
+            example: 'gitlab-org/gitlab',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+          queryParam({
+            name: 'ref',
+            example: 'master',
+          }),
+        ],
       },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: this.render({ commitDate: '2013-07-31T20:01:41Z' }),
-      documentation: defaultDocumentation,
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'last commit' }
 
diff --git a/services/gitlab/gitlab-license.service.js b/services/gitlab/gitlab-license.service.js
index 131173ba8a..54aa99fb98 100644
--- a/services/gitlab/gitlab-license.service.js
+++ b/services/gitlab/gitlab-license.service.js
@@ -1,7 +1,8 @@
 import Joi from 'joi'
+import { pathParam, queryParam } from '../index.js'
 import { optionalUrl } from '../validators.js'
 import { renderLicenseBadge } from '../licenses.js'
-import { documentation, httpErrorsFor } from './gitlab-helper.js'
+import { description, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.object({
@@ -23,33 +24,24 @@ export default class GitlabLicense extends GitLabBase {
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'GitLab',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
+  static openApi = {
+    '/gitlab/license/{project}': {
+      get: {
+        summary: 'GitLab License',
+        description,
+        parameters: [
+          pathParam({
+            name: 'project',
+            example: 'gitlab-org/gitlab',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+        ],
       },
-      staticPreview: {
-        label: 'license',
-        message: 'MIT License',
-        color: 'green',
-      },
-      documentation,
     },
-    {
-      title: 'GitLab (self-managed)',
-      namedParams: {
-        project: 'gitlab-cn/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://jihulab.com' },
-      staticPreview: {
-        label: 'license',
-        message: 'MIT License',
-        color: 'green',
-      },
-      documentation,
-    },
-  ]
+  }
 
   static defaultBadgeData = { label: 'license' }
 
diff --git a/services/gitlab/gitlab-license.tester.js b/services/gitlab/gitlab-license.tester.js
index e9caceb74f..8ddfaf25af 100644
--- a/services/gitlab/gitlab-license.tester.js
+++ b/services/gitlab/gitlab-license.tester.js
@@ -24,11 +24,22 @@ t.create('License for repo without a license')
     color: 'lightgrey',
   })
 
-t.create('Other license').get('/gitlab-org/gitlab-foss.json').expectBadge({
-  label: 'license',
-  message: 'Other',
-  color: unknownLicenseColor,
-})
+t.create('Other license')
+  .get('/group/project.json')
+  .intercept(nock =>
+    nock('https://gitlab.com')
+      .get('/api/v4/projects/group%2Fproject?license=1')
+      .reply(200, {
+        license: {
+          name: 'Other',
+        },
+      }),
+  )
+  .expectBadge({
+    label: 'license',
+    message: 'Other',
+    color: unknownLicenseColor,
+  })
 
 t.create('License for unknown repo')
   .get('/user1/gitlab-does-not-have-this-repo.json')
diff --git a/services/gitlab/gitlab-merge-requests.service.js b/services/gitlab/gitlab-merge-requests.service.js
index 3ba340331d..62dc3560c2 100644
--- a/services/gitlab/gitlab-merge-requests.service.js
+++ b/services/gitlab/gitlab-merge-requests.service.js
@@ -1,7 +1,8 @@
 import Joi from 'joi'
+import { pathParam, queryParam } from '../index.js'
 import { optionalUrl, nonNegativeInteger } from '../validators.js'
 import { metric } from '../text-formatters.js'
-import { documentation, httpErrorsFor } from './gitlab-helper.js'
+import { description, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 // The total number of MR is in the `x-total` field in the headers.
@@ -22,258 +23,47 @@ const more = `
 </p>
 `
 
-const labelText = `
-<p>
-  If you want to use multiple labels then please use commas (<code>,</code>) to separate them, e.g. <code>foo,bar</code>.
-</p>
-`
-
-const defaultDocumentation = documentation + more
-
-const labelDocumentation = documentation + labelText + more
+const mergeRequestsDescription = description + more
 
 export default class GitlabMergeRequests extends GitLabBase {
   static category = 'issue-tracking'
 
   static route = {
     base: 'gitlab/merge-requests',
-    pattern: ':variant(all|open|closed|locked|merged):raw(-raw)?/:project+',
+    pattern:
+      ':variant(all|all-raw|open|open-raw|closed|closed-raw|locked|locked-raw|merged|merged-raw)/:project+',
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'GitLab merge requests',
-      pattern: 'open/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'merge requests',
-        message: '1.4k open',
-        color: 'blue',
-      },
-      documentation: defaultDocumentation,
-    },
-    {
-      title: 'GitLab merge requests',
-      pattern: 'open-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'open merge requests',
-        message: '1.4k',
-        color: 'blue',
-      },
-      documentation: defaultDocumentation,
-    },
-    {
-      title: 'GitLab merge requests by-label',
-      pattern: 'open/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,type::feature',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'test,failure::new merge requests',
-        message: '3 open',
-        color: 'blue',
-      },
-      documentation: labelDocumentation,
-    },
-    {
-      title: 'GitLab merge requests by-label',
-      pattern: 'open-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'gitlab-org/gitlab',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'open test,failure::new merge requests',
-        message: '3',
-        color: 'blue',
-      },
-      documentation: labelDocumentation,
-    },
-    {
-      title: 'GitLab closed merge requests',
-      pattern: 'closed/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'merge requests',
-        message: 'more than 10k closed',
-        color: 'blue',
-      },
-      documentation: defaultDocumentation,
-    },
-    {
-      title: 'GitLab closed merge requests',
-      pattern: 'closed-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'closed merge requests',
-        message: 'more than 10k',
-        color: 'blue',
-      },
-      documentation: defaultDocumentation,
-    },
-    {
-      title: 'GitLab closed merge requests by-label',
-      pattern: 'closed/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,type::feature',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'test,failure::new merge requests',
-        message: '32 closed',
-        color: 'blue',
-      },
-      documentation: labelDocumentation,
-    },
-    {
-      title: 'GitLab closed merge requests by-label',
-      pattern: 'closed-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,type::feature',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'closed test,failure::new merge requests',
-        message: '32',
-        color: 'blue',
-      },
-      documentation: labelDocumentation,
-    },
-    {
-      title: 'GitLab all merge requests',
-      pattern: 'all/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'merge requests',
-        message: 'more than 10k all',
-        color: 'blue',
-      },
-      documentation: defaultDocumentation,
-    },
-    {
-      title: 'GitLab all merge requests',
-      pattern: 'all-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'all merge requests',
-        message: 'more than 10k',
-        color: 'blue',
-      },
-      documentation: defaultDocumentation,
-    },
-    {
-      title: 'GitLab all merge requests by-label',
-      pattern: 'all-raw/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,failure::new',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'all test,failure::new merge requests',
-        message: '12',
-        color: 'blue',
-      },
-      documentation: labelDocumentation,
-    },
-    {
-      title: 'GitLab locked merge requests',
-      pattern: 'locked/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'merge requests',
-        message: '0 locked',
-        color: 'blue',
-      },
-      documentation: defaultDocumentation,
-    },
-    {
-      title: 'GitLab locked merge requests by-label',
-      pattern: 'closed/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,type::feature',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'test,failure::new merge requests',
-        message: '0 locked',
-        color: 'blue',
-      },
-      documentation: labelDocumentation,
-    },
-    {
-      title: 'GitLab merged merge requests',
-      pattern: 'merged/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'merge requests',
-        message: 'more than 10k merged',
-        color: 'blue',
+  static openApi = {
+    '/gitlab/merge-requests/{variant}/{project}': {
+      get: {
+        summary: 'GitLab Merge Requests',
+        description: mergeRequestsDescription,
+        parameters: [
+          pathParam({
+            name: 'variant',
+            example: 'all',
+            schema: { type: 'string', enum: this.getEnum('variant') },
+          }),
+          pathParam({
+            name: 'project',
+            example: 'gitlab-org/gitlab',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+          queryParam({
+            name: 'labels',
+            example: 'test,type::feature',
+            description:
+              'If you want to use multiple labels, you can use a comma (<code>,</code>) to separate them, e.g. <code>foo,bar</code>',
+          }),
+        ],
       },
-      documentation: defaultDocumentation,
     },
-    {
-      title: 'GitLab merged merge requests by-label',
-      pattern: 'merged/:project+',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
-      },
-      queryParams: {
-        labels: 'test,type::feature',
-        gitlab_url: 'https://gitlab.com',
-      },
-      staticPreview: {
-        label: 'test,failure::new merge requests',
-        message: '68 merged',
-        color: 'blue',
-      },
-      documentation: labelDocumentation,
-    },
-  ]
+  }
 
   static defaultBadgeData = { label: 'merge requests' }
 
@@ -284,7 +74,7 @@ export default class GitlabMergeRequests extends GitLabBase {
 
     let labelPrefix = ''
     let messageSuffix = ''
-    if (raw !== undefined) {
+    if (raw) {
       labelPrefix = `${state} `
     } else {
       messageSuffix = state
@@ -332,19 +122,19 @@ export default class GitlabMergeRequests extends GitLabBase {
   }
 
   async handle(
-    { variant, raw, project },
+    { variant, project },
     { gitlab_url: baseUrl = 'https://gitlab.com', labels },
   ) {
     const data = await this.fetch({
       project,
       baseUrl,
-      variant,
+      variant: variant.split('-')[0],
       labels,
     })
     const mergeRequestCount = this.constructor.transform(data)
     return this.constructor.render({
-      variant,
-      raw,
+      variant: variant.split('-')[0],
+      raw: variant.endsWith('-raw'),
       labels,
       mergeRequestCount,
     })
diff --git a/services/gitlab/gitlab-pipeline-coverage.service.js b/services/gitlab/gitlab-pipeline-coverage.service.js
index e844f43fb7..4829d6c946 100644
--- a/services/gitlab/gitlab-pipeline-coverage.service.js
+++ b/services/gitlab/gitlab-pipeline-coverage.service.js
@@ -1,8 +1,13 @@
 import Joi from 'joi'
 import { coveragePercentage } from '../color-formatters.js'
 import { optionalUrl } from '../validators.js'
-import { BaseSvgScrapingService, NotFound } from '../index.js'
-import { documentation, httpErrorsFor } from './gitlab-helper.js'
+import {
+  BaseSvgScrapingService,
+  NotFound,
+  pathParam,
+  queryParam,
+} from '../index.js'
+import { description, httpErrorsFor } from './gitlab-helper.js'
 
 const schema = Joi.object({
   message: Joi.string()
@@ -47,40 +52,32 @@ export default class GitlabPipelineCoverage extends BaseSvgScrapingService {
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'Gitlab code coverage',
-      namedParams: { project: 'gitlab-org/gitlab-runner' },
-      queryParams: { branch: 'master' },
-      staticPreview: this.render({ coverage: 67 }),
-      documentation: documentation + moreDocs,
-    },
-    {
-      title: 'Gitlab code coverage (specific job)',
-      namedParams: { project: 'gitlab-org/gitlab-runner' },
-      queryParams: { job_name: 'test coverage report', branch: 'master' },
-      staticPreview: this.render({ coverage: 96 }),
-      documentation: documentation + moreDocs,
-    },
-    {
-      title: 'Gitlab code coverage (self-managed)',
-      namedParams: { project: 'GNOME/at-spi2-core' },
-      queryParams: { gitlab_url: 'https://gitlab.gnome.org', branch: 'master' },
-      staticPreview: this.render({ coverage: 93 }),
-      documentation: documentation + moreDocs,
-    },
-    {
-      title: 'Gitlab code coverage (self-managed, specific job)',
-      namedParams: { project: 'GNOME/libhandy' },
-      queryParams: {
-        gitlab_url: 'https://gitlab.gnome.org',
-        job_name: 'unit-test',
-        branch: 'master',
+  static openApi = {
+    '/gitlab/pipeline-coverage/{project}': {
+      get: {
+        summary: 'Gitlab Code Coverage',
+        description: description + moreDocs,
+        parameters: [
+          pathParam({
+            name: 'project',
+            example: 'gitlab-org/gitlab',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+          queryParam({
+            name: 'job_name',
+            example: 'jest-integration',
+          }),
+          queryParam({
+            name: 'branch',
+            example: 'master',
+          }),
+        ],
       },
-      staticPreview: this.render({ coverage: 93 }),
-      documentation: documentation + moreDocs,
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'coverage' }
 
diff --git a/services/gitlab/gitlab-pipeline-coverage.tester.js b/services/gitlab/gitlab-pipeline-coverage.tester.js
index c200646d24..eed060cd0a 100644
--- a/services/gitlab/gitlab-pipeline-coverage.tester.js
+++ b/services/gitlab/gitlab-pipeline-coverage.tester.js
@@ -23,11 +23,19 @@ t.create('Coverage (nonexistent branch)')
     message: 'not set up',
   })
 
+// Gitlab will redirect users to a sign-in page
+// (which we ultimately see as a 403 error) in the event
+// a nonexistent, or private, repository is specified.
+// Given the additional complexity that would've been required to
+// present users with a more traditional and friendly 'Not Found'
+// error message, we will simply display invalid
+// https://github.com/badges/shields/pull/5538
+// https://github.com/badges/shields/pull/9752
 t.create('Coverage (nonexistent repo)')
   .get('/this-repo/does-not-exist.json')
   .expectBadge({
     label: 'coverage',
-    message: 'inaccessible',
+    message: 'invalid',
   })
 
 t.create('Coverage (custom job)')
diff --git a/services/gitlab/gitlab-pipeline-status.service.js b/services/gitlab/gitlab-pipeline-status.service.js
index 2b9a5ced8f..e5d03c9d7a 100644
--- a/services/gitlab/gitlab-pipeline-status.service.js
+++ b/services/gitlab/gitlab-pipeline-status.service.js
@@ -1,8 +1,14 @@
 import Joi from 'joi'
 import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js'
 import { optionalUrl } from '../validators.js'
-import { BaseSvgScrapingService, NotFound, redirector } from '../index.js'
-import { documentation, httpErrorsFor } from './gitlab-helper.js'
+import {
+  BaseSvgScrapingService,
+  NotFound,
+  redirector,
+  pathParam,
+  queryParam,
+} from '../index.js'
+import { description, httpErrorsFor } from './gitlab-helper.js'
 
 const badgeSchema = Joi.object({
   message: Joi.alternatives()
@@ -46,22 +52,28 @@ class GitlabPipelineStatus extends BaseSvgScrapingService {
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'Gitlab pipeline status',
-      namedParams: { project: 'gitlab-org/gitlab' },
-      queryParams: { branch: 'master' },
-      staticPreview: this.render({ status: 'passed' }),
-      documentation: documentation + moreDocs,
+  static openApi = {
+    '/gitlab/pipeline-status/{project}': {
+      get: {
+        summary: 'Gitlab Pipeline Status',
+        description: description + moreDocs,
+        parameters: [
+          pathParam({
+            name: 'project',
+            example: 'gitlab-org/gitlab',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+          queryParam({
+            name: 'branch',
+            example: 'master',
+          }),
+        ],
+      },
     },
-    {
-      title: 'Gitlab pipeline status (self-managed)',
-      namedParams: { project: 'GNOME/pango' },
-      queryParams: { gitlab_url: 'https://gitlab.gnome.org', branch: 'master' },
-      staticPreview: this.render({ status: 'passed' }),
-      documentation: documentation + moreDocs,
-    },
-  ]
+  }
 
   static render({ status }) {
     return renderBuildStatusBadge({ status })
diff --git a/services/gitlab/gitlab-pipeline-status.tester.js b/services/gitlab/gitlab-pipeline-status.tester.js
index 56e8f15ac3..c13cc26609 100644
--- a/services/gitlab/gitlab-pipeline-status.tester.js
+++ b/services/gitlab/gitlab-pipeline-status.tester.js
@@ -30,17 +30,18 @@ t.create('Pipeline status (nonexistent branch)')
   })
 
 // Gitlab will redirect users to a sign-in page
-// (which we ultimately see as a 503 error) in the event
+// (which we ultimately see as a 403 error) in the event
 // a nonexistent, or private, repository is specified.
 // Given the additional complexity that would've been required to
 // present users with a more traditional and friendly 'Not Found'
-// error message, we will simply display inaccessible
+// error message, we will simply display invalid
 // https://github.com/badges/shields/pull/5538
+// https://github.com/badges/shields/pull/9752
 t.create('Pipeline status (nonexistent repo)')
   .get('/pipeline-status/this-repo/does-not-exist.json?branch=master')
   .expectBadge({
     label: 'build',
-    message: 'inaccessible',
+    message: 'invalid',
   })
 
 t.create('Pipeline status (custom gitlab URL)')
diff --git a/services/gitlab/gitlab-release.service.js b/services/gitlab/gitlab-release.service.js
index 18a2789c7f..81cac9ce19 100644
--- a/services/gitlab/gitlab-release.service.js
+++ b/services/gitlab/gitlab-release.service.js
@@ -2,7 +2,7 @@ import Joi from 'joi'
 import { optionalUrl } from '../validators.js'
 import { latest, renderVersionBadge } from '../version.js'
 import { NotFound, pathParam, queryParam } from '../index.js'
-import { documentation, httpErrorsFor } from './gitlab-helper.js'
+import { description, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.array().items(
@@ -43,7 +43,7 @@ export default class GitLabRelease extends GitLabBase {
     '/gitlab/v/release/{project}': {
       get: {
         summary: 'GitLab Release',
-        description: documentation,
+        description,
         parameters: [
           pathParam({
             name: 'project',
diff --git a/services/gitlab/gitlab-stars.service.js b/services/gitlab/gitlab-stars.service.js
index 280f9fdbd9..11f67ffb30 100644
--- a/services/gitlab/gitlab-stars.service.js
+++ b/services/gitlab/gitlab-stars.service.js
@@ -1,8 +1,9 @@
 import Joi from 'joi'
+import { pathParam, queryParam } from '../index.js'
 import { optionalUrl, nonNegativeInteger } from '../validators.js'
 import { metric } from '../text-formatters.js'
 import GitLabBase from './gitlab-base.js'
-import { documentation } from './gitlab-helper.js'
+import { description } from './gitlab-helper.js'
 
 const schema = Joi.object({
   star_count: nonNegativeInteger,
@@ -21,21 +22,24 @@ export default class GitlabStars extends GitLabBase {
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'GitLab stars',
-      namedParams: {
-        project: 'gitlab-org/gitlab',
+  static openApi = {
+    '/gitlab/stars/{project}': {
+      get: {
+        summary: 'GitLab Stars',
+        description,
+        parameters: [
+          pathParam({
+            name: 'project',
+            example: 'gitlab-org/gitlab',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+        ],
       },
-      queryParams: { gitlab_url: 'https://gitlab.com' },
-      staticPreview: {
-        label: 'stars',
-        message: '3.9k',
-        style: 'social',
-      },
-      documentation,
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'stars', namedLogo: 'gitlab' }
 
diff --git a/services/gitlab/gitlab-tag.service.js b/services/gitlab/gitlab-tag.service.js
index 650a7d40a5..43fbd0dc0d 100644
--- a/services/gitlab/gitlab-tag.service.js
+++ b/services/gitlab/gitlab-tag.service.js
@@ -3,8 +3,8 @@ import { version as versionColor } from '../color-formatters.js'
 import { optionalUrl } from '../validators.js'
 import { latest } from '../version.js'
 import { addv } from '../text-formatters.js'
-import { NotFound } from '../index.js'
-import { documentation, httpErrorsFor } from './gitlab-helper.js'
+import { NotFound, pathParam, queryParam } from '../index.js'
+import { description, httpErrorsFor } from './gitlab-helper.js'
 import GitLabBase from './gitlab-base.js'
 
 const schema = Joi.array().items(
@@ -13,19 +13,16 @@ const schema = Joi.array().items(
   }),
 )
 
+const sortEnum = ['date', 'semver']
+
 const queryParamSchema = Joi.object({
   gitlab_url: optionalUrl,
   include_prereleases: Joi.equal(''),
-  sort: Joi.string().valid('date', 'semver').default('date'),
+  sort: Joi.string()
+    .valid(...sortEnum)
+    .default('date'),
 }).required()
 
-const commonProps = {
-  namedParams: {
-    project: 'shields-ops-group/tag-test',
-  },
-  documentation,
-}
-
 export default class GitlabTag extends GitLabBase {
   static category = 'version'
 
@@ -35,42 +32,34 @@ export default class GitlabTag extends GitLabBase {
     queryParamSchema,
   }
 
-  static examples = [
-    {
-      title: 'GitLab tag (latest by date)',
-      ...commonProps,
-      queryParams: { sort: 'date' },
-      staticPreview: this.render({ version: 'v2.0.0' }),
-    },
-    {
-      title: 'GitLab tag (latest by SemVer)',
-      ...commonProps,
-      queryParams: { sort: 'semver' },
-      staticPreview: this.render({ version: 'v4.0.0' }),
-    },
-    {
-      title: 'GitLab tag (latest by SemVer pre-release)',
-      ...commonProps,
-      queryParams: {
-        sort: 'semver',
-        include_prereleases: null,
-      },
-      staticPreview: this.render({ version: 'v5.0.0-beta.1', sort: 'semver' }),
-    },
-    {
-      title: 'GitLab tag (self-managed)',
-      namedParams: {
-        project: 'GNOME/librsvg',
-      },
-      documentation,
-      queryParams: {
-        sort: 'semver',
-        include_prereleases: null,
-        gitlab_url: 'https://gitlab.gnome.org',
+  static openApi = {
+    '/gitlab/v/tag/{project}': {
+      get: {
+        summary: 'GitLab Tag',
+        description,
+        parameters: [
+          pathParam({
+            name: 'project',
+            example: 'shields-ops-group/tag-test',
+          }),
+          queryParam({
+            name: 'gitlab_url',
+            example: 'https://gitlab.com',
+          }),
+          queryParam({
+            name: 'include_prereleases',
+            schema: { type: 'boolean' },
+            example: null,
+          }),
+          queryParam({
+            name: 'sort',
+            schema: { type: 'string', enum: sortEnum },
+            example: 'semver',
+          }),
+        ],
       },
-      staticPreview: this.render({ version: 'v2.51.4' }),
     },
-  ]
+  }
 
   static defaultBadgeData = { label: 'tag' }
 
-- 
GitLab