mirror of
https://gitea.com/gitea/docs.git
synced 2026-09-19 04:28:53 +00:00
feat(api): render and publish the openapi 3.0 documents (#514)
Closes #37. Gitea generates an OpenAPI 3.0 document since 1.27 (`templates/swagger/v1_openapi3_json.tmpl`, renamed to `templates/swagger/v1-openapi3.generated.json` on main, served by an instance at `/openapi3.v1.json`), but this site never picked it up and still published Swagger 2.0 only. That is what the issue is about: code generators such as `openapi-python-client` reject the document with "You may be trying to use a Swagger document; this is not supported by this project". **What this does** - `update_api_docs.sh` downloads the OpenAPI 3.0 document next to the Swagger 2.0 one, rewrites the same placeholders (they sit in `servers` instead of `basePath`) and writes `static/openapi3-latest.json` and `static/openapi3-<minor>.json`. Versions that do not have one (1.26 and older) are skipped with a message and keep their Swagger 2.0 document. - The OpenAPI 3.0 document is rendered where it exists (`/api/` and `/api/next/`), the older versions keep rendering Swagger 2.0. Both documents describe the same api and carry the same operation ids, so no url of an operation page changes; the `basePath` normalization is now only applied to the Swagger 2.0 documents, since a full url in `servers` is valid. - The overview page of each version links both documents for download, which is what the issue actually needs: `https://docs.gitea.com/openapi3-27.json` for the current release, `https://docs.gitea.com/openapi3-latest.json` for gitea main. - The scheduled workflow now refreshes and proposes both documents of gitea main. **Verification** - `pnpm dlx @redocly/cli lint static/openapi3-latest.json`: valid, only content warnings that come from upstream (missing 4xx responses, ambiguous paths). - `openapi-python-client generate --path static/openapi3-latest.json`, the tool from the issue, generates a client. The only warning left is `POST /markdown/raw`, whose request body is `text/plain`. - Operation ids of `static/swagger-27.json` and `static/openapi3-27.json` are identical (481 on both sides), so the operation pages keep their urls. - Built the api product and compared a rendered operation page against the Swagger 2.0 one: same sections, the request body is now marked required where the document says so. The released Swagger 2.0 documents are kept: they are the only description 1.26 and older ever produced, and some tooling still expects them. <!-- cloudflare-preview --> Preview: https://pr-514.docs-gitea-com.pages.dev Reviewed-on: https://gitea.com/gitea/docs/pulls/514 Reviewed-by: bircni <[email protected]>
This commit is contained in:
+19
-9
@@ -7,8 +7,8 @@ deployment and are linked from the header.
|
||||
|
||||
The content directories are the ones the site has always used — `docs/`,
|
||||
`versioned_docs/`, `i18n/`, `runner-docs/`, `runner-docs_versioned_docs/` and
|
||||
`static/swagger-*.json` — so the release scripts and the translation workflow
|
||||
stay unchanged.
|
||||
`static/swagger-*.json` (plus `static/openapi3-*.json` since gitea 1.27) — so
|
||||
the release scripts and the translation workflow stay unchanged.
|
||||
|
||||
## Running it
|
||||
|
||||
@@ -40,8 +40,9 @@ pickers, the version banner, the search facets and the api schemas.
|
||||
| `i18n/zh-cn/docusaurus-plugin-content-docs/version-1.27/` | `/zh-cn/` |
|
||||
| `runner-docs_versioned_docs/version-3/` | `/runner/` |
|
||||
| `runner-docs/` | `/runner/develop/` |
|
||||
| `static/swagger-27.json` | `/api/` |
|
||||
| `static/swagger-latest.json` | `/api/next/` |
|
||||
| `static/openapi3-27.json` | `/api/` |
|
||||
| `static/openapi3-latest.json` | `/api/next/` |
|
||||
| `static/swagger-26.json` | `/api/1.26/` |
|
||||
|
||||
The loader reads the markdown in two passes. The first one works out the route
|
||||
of every file, the second normalizes and renders it:
|
||||
@@ -81,12 +82,21 @@ and `/api/1.27/` are redirected onto them in `cloudflare/_redirects`.
|
||||
|
||||
## Api reference
|
||||
|
||||
`starlight-openapi` generates a page per operation from the seven swagger
|
||||
documents `update_api_docs.sh` maintains. Two adjustments happen at build time:
|
||||
`starlight-openapi` generates a page per operation from the documents
|
||||
`update_api_docs.sh` maintains: the openapi 3.0 document for the versions that
|
||||
have one (gitea >= 1.27), the swagger 2.0 one for the older versions. Both
|
||||
describe the same api and use the same operation ids, so the urls of the
|
||||
operation pages do not depend on which one is rendered. Three adjustments
|
||||
happen at build time:
|
||||
|
||||
- the documents set `basePath` to the full `https://gitea.com/api/v1` url, which
|
||||
redoc accepted but is not valid swagger 2.0. A normalized copy is written to
|
||||
`.cache/openapi/` instead of touching the sources.
|
||||
- the swagger 2.0 documents set `basePath` to the full
|
||||
`https://gitea.com/api/v1` url, which redoc accepted but is not valid swagger
|
||||
2.0. A normalized copy is written to `.cache/openapi/` instead of touching the
|
||||
sources. The openapi 3.0 documents keep that url in `servers`, where it is
|
||||
valid, and only get the operation ids and the tag descriptions adjusted.
|
||||
- both documents of a version are linked for download from its overview page,
|
||||
see `specDownloads()` in `src/config/api.ts` and
|
||||
`src/components/ApiOverviewTags.astro`.
|
||||
- the plugin slugifies the base path, so `/api/1.26/` is generated as
|
||||
`/api/126/`. `src/integrations/postbuild.ts` renames the directories and
|
||||
rewrites the links once the build is done.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import { Card, CardGrid } from '@astrojs/starlight/components';
|
||||
import { specDownloads } from '../config/api';
|
||||
|
||||
/**
|
||||
* Replaces the "Operations" section of the api overview, which lists every
|
||||
@@ -9,6 +10,9 @@ import { Card, CardGrid } from '@astrojs/starlight/components';
|
||||
* Substituted for `starlight-openapi/components/overview/OverviewNavigationLinks.astro`
|
||||
* by the `gitea-openapi-overview` vite plugin in `astro.config.mjs`, so the
|
||||
* props are the ones that component receives.
|
||||
*
|
||||
* The documents of the version are offered for download above the cards, the
|
||||
* rendered pages are of no use to a code generator.
|
||||
*/
|
||||
interface NavigationGroup {
|
||||
label: string;
|
||||
@@ -20,6 +24,9 @@ interface NavigationGroup {
|
||||
const { groups } = Astro.props as { groups: NavigationGroup[] };
|
||||
const visible = groups.filter((group) => group.links.length > 0);
|
||||
|
||||
const meta = Astro.locals.starlightRoute.entry.data.gitea;
|
||||
const downloads = meta ? specDownloads(meta.version) : [];
|
||||
|
||||
/**
|
||||
* Page of a tag, `/api/operations/tags/repository/`. Derived from the first
|
||||
* operation link of the group, which is `<base>/operations/<operation>/`, so it
|
||||
@@ -43,6 +50,21 @@ function count(group: NavigationGroup): string {
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
downloads.length > 0 && (
|
||||
<p class="gitea-api-downloads">
|
||||
Download the specification:
|
||||
{downloads.map((download, index) => (
|
||||
<>
|
||||
{index > 0 && ', '}
|
||||
<a href={download.href} download>
|
||||
{download.label}
|
||||
</a>
|
||||
</>
|
||||
))}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
{
|
||||
visible.length > 0 && (
|
||||
<div class="gitea-api-overview">
|
||||
@@ -73,4 +95,12 @@ function count(group: NavigationGroup): string {
|
||||
font-size: var(--sl-text-sm);
|
||||
color: var(--sl-color-gray-3);
|
||||
}
|
||||
|
||||
.gitea-api-downloads {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.gitea-api-downloads a {
|
||||
margin-inline-start: 0.35rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,6 +28,7 @@ interface Operation {
|
||||
}
|
||||
|
||||
interface SwaggerDocument {
|
||||
openapi?: string;
|
||||
basePath?: string;
|
||||
host?: string;
|
||||
schemes?: string[];
|
||||
@@ -75,6 +76,9 @@ function hyphenateOperationIds(document: SwaggerDocument): void {
|
||||
* `https://gitea.com/api/v1` url, which redoc accepted but is not valid swagger
|
||||
* 2.0: the host belongs into `host` and the scheme into `schemes`. Normalize a
|
||||
* copy instead of touching the sources, which the docusaurus site still reads.
|
||||
*
|
||||
* The openapi 3.0 documents keep the url in `servers`, where a full url is
|
||||
* valid, so only the operation ids and the tags are touched for those.
|
||||
*/
|
||||
function normalizeSchema(source: string, name: string): string {
|
||||
const document = JSON.parse(readFileSync(source, 'utf-8')) as SwaggerDocument;
|
||||
@@ -129,7 +133,12 @@ export const apiSchemas = getProduct('api')
|
||||
.map((version) => ({
|
||||
base: ['api', version.path].filter(Boolean).join('/'),
|
||||
label: `API ${version.label}`,
|
||||
schema: normalizeSchema(path.join(repoRoot, version.schema!), `${version.id}.json`),
|
||||
// gitea generates an openapi 3.0 document since 1.27, render it where it
|
||||
// exists: it is the same api, described in the format the tooling expects
|
||||
schema: normalizeSchema(
|
||||
path.join(repoRoot, version.openapi3 ?? version.schema!),
|
||||
`${version.id}.json`,
|
||||
),
|
||||
// the schema group is lifted to the top level of the sidebar in
|
||||
// src/middleware/api.ts, so the tag groups below it start collapsed and
|
||||
// starlight opens the one holding the current operation
|
||||
@@ -140,3 +149,28 @@ export const apiSchemas = getProduct('api')
|
||||
operations: { badges: true },
|
||||
},
|
||||
}));
|
||||
|
||||
export interface SpecDownload {
|
||||
label: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The documents of a version, as they are served from `static/`. Shown on the
|
||||
* overview page of the version: the rendered reference is not something a code
|
||||
* generator can read, the documents are.
|
||||
*/
|
||||
export function specDownloads(versionId: string): SpecDownload[] {
|
||||
const version = getProduct('api').versions.find((candidate) => candidate.id === versionId);
|
||||
if (!version) return [];
|
||||
|
||||
const downloads: SpecDownload[] = [];
|
||||
if (version.openapi3) downloads.push({ label: 'OpenAPI 3.0', href: staticHref(version.openapi3) });
|
||||
if (version.schema) downloads.push({ label: 'Swagger 2.0', href: staticHref(version.schema) });
|
||||
return downloads;
|
||||
}
|
||||
|
||||
/** `static/openapi3-27.json` is published as `/openapi3-27.json`. */
|
||||
function staticHref(file: string): string {
|
||||
return `/${file.replace(/^static\//, '')}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user