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:
Lunny Xiao
2026-08-14 18:31:00 +00:00
parent 5c78ad533d
commit 4e38ace168
10 changed files with 71941 additions and 47 deletions
+30 -19
View File
@@ -1,4 +1,4 @@
name: update swagger files
name: update api spec files
on:
schedule:
@@ -9,6 +9,8 @@ env:
# main is protected, so the update is proposed as a pull request from this branch
BOT_BRANCH: bot/update-swagger-latest
BASE_BRANCH: main
# the documents of the gitea main branch, refreshed by this job
LATEST_FILES: static/swagger-latest.json static/openapi3-latest.json
jobs:
update-swagger:
@@ -20,19 +22,21 @@ jobs:
ref: main
# pushing uses DEPLOY_TOKEN, keep the ephemeral job token out of .git/config
persist-credentials: false
- name: regenerate swagger-latest.json
- name: regenerate the latest api documents
run: make update-api-docs-latest
- name: verify the generated file
run: |
set -euo pipefail
# placeholders must have been replaced, otherwise upstream changed them again
if grep -q -e 'GITEA-API-APP' -e '{{' static/swagger-latest.json; then
echo "static/swagger-latest.json still contains template placeholders"
exit 1
fi
if command -v python3 >/dev/null 2>&1; then
python3 -c "import json; json.load(open('static/swagger-latest.json'))"
fi
for file in $LATEST_FILES; do
# placeholders must have been replaced, otherwise upstream changed them again
if grep -q -e 'GITEA-API-APP' -e '{{' "$file"; then
echo "$file still contains template placeholders"
exit 1
fi
if command -v python3 >/dev/null 2>&1; then
python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$file"
fi
done
- name: push bot branch
id: bot_branch
env:
@@ -40,8 +44,8 @@ jobs:
run: |
set -euo pipefail
if git diff --quiet -- static/swagger-latest.json; then
echo "swagger-latest.json is already up to date"
if git diff --quiet -- $LATEST_FILES; then
echo "the api documents are already up to date"
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
@@ -54,9 +58,16 @@ jobs:
# skip if an open bot branch already carries exactly this file
if git fetch --quiet --depth=1 "$remote" "refs/heads/$BOT_BRANCH" 2>/dev/null; then
old_blob="$(git rev-parse --quiet --verify "FETCH_HEAD:static/swagger-latest.json" || true)"
if [ "$old_blob" = "$(git hash-object static/swagger-latest.json)" ]; then
echo "$BOT_BRANCH already proposes this swagger-latest.json"
same=true
for file in $LATEST_FILES; do
old_blob="$(git rev-parse --quiet --verify "FETCH_HEAD:$file" || true)"
if [ "$old_blob" != "$(git hash-object "$file")" ]; then
same=false
break
fi
done
if [ "$same" = true ]; then
echo "$BOT_BRANCH already proposes these api documents"
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
@@ -65,8 +76,8 @@ jobs:
git config user.name "Gitea Bot"
git config user.email "[email protected]"
git switch --create "$BOT_BRANCH"
git add static/swagger-latest.json
git commit -m "Update swagger-latest.json"
git add $LATEST_FILES
git commit -m "Update the latest api documents"
# force push: the branch is always rebuilt on top of the current main
git push --force "$remote" "HEAD:refs/heads/$BOT_BRANCH"
echo "changed=true" >> "$GITHUB_OUTPUT"
@@ -81,8 +92,8 @@ jobs:
{
"base": "$BASE_BRANCH",
"head": "$BOT_BRANCH",
"title": "Update swagger-latest.json",
"body": "Automated update of static/swagger-latest.json from the gitea main branch, opened by the \"update swagger files\" scheduled workflow."
"title": "Update the latest api documents",
"body": "Automated update of static/swagger-latest.json and static/openapi3-latest.json from the gitea main branch, opened by the \"update api spec files\" scheduled workflow."
}
EOF