Skip to content

Commit 9e031eb

Browse files
myersclaudesilverwindwxiaoguang
authored
Serve OpenAPI 3.0 spec at /openapi.v1.json (#37038)
Add a build-time conversion step that transforms the existing Swagger 2.0 spec into an OpenAPI 3.0 spec. The OAS3 spec is served alongside the existing Swagger 2.0 spec, enabling API clients that require OAS3 to generate code directly from Gitea's API. This is not to be an answer to how gitea handles OAS3 long term, but a way to use what we have to move a step forward. --------- Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1 parent 18762c7 commit 9e031eb

39 files changed

Lines changed: 34700 additions & 99 deletions

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ indent_style = tab
1818
[templates/custom/*.tmpl]
1919
insert_final_newline = false
2020

21-
[templates/swagger/v1_json.tmpl]
21+
[templates/swagger/*_json.tmpl]
2222
indent_style = space
2323
insert_final_newline = false
2424

Makefile

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ ESLINT_CONCURRENCY ?= 2
151151
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
152152
SWAGGER_SPEC_INPUT := templates/swagger/v1_input.json
153153
SWAGGER_EXCLUDE := code.gitea.io/sdk
154+
OPENAPI3_SPEC := templates/swagger/v1_openapi3_json.tmpl
154155

155156
TEST_MYSQL_HOST ?= mysql:3306
156157
TEST_MYSQL_DBNAME ?= testgitea
@@ -233,7 +234,7 @@ TAGS_PREREQ := $(TAGS_EVIDENCE)
233234
endif
234235

235236
.PHONY: generate-swagger
236-
generate-swagger: $(SWAGGER_SPEC) ## generate the swagger spec from code comments
237+
generate-swagger: $(SWAGGER_SPEC) $(OPENAPI3_SPEC) ## generate the swagger spec from code comments
237238

238239
$(SWAGGER_SPEC): $(GO_SOURCES) $(SWAGGER_SPEC_INPUT)
239240
$(GO) run $(SWAGGER_PACKAGE) generate spec --exclude "$(SWAGGER_EXCLUDE)" --input "$(SWAGGER_SPEC_INPUT)" --output './$(SWAGGER_SPEC)'
@@ -255,14 +256,29 @@ swagger-validate: ## check if the swagger spec is valid
255256
$(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)'
256257
@$(SED_INPLACE) -E -e 's|"basePath":( *)"/(.*)"|"basePath":\1"\2"|g' './$(SWAGGER_SPEC)' # remove the prefix slash from basePath
257258

259+
.PHONY: generate-openapi3
260+
generate-openapi3: $(OPENAPI3_SPEC) ## generate the OpenAPI 3.0 spec from the Swagger 2.0 spec
261+
262+
$(OPENAPI3_SPEC): $(SWAGGER_SPEC) build/generate-openapi.go $(wildcard build/openapi3gen/*.go)
263+
$(GO) run build/generate-openapi.go
264+
265+
.PHONY: openapi3-check
266+
openapi3-check: generate-openapi3
267+
@diff=$$(git diff --color=always '$(OPENAPI3_SPEC)'); \
268+
if [ -n "$$diff" ]; then \
269+
echo "Please run 'make generate-openapi3' and commit the result:"; \
270+
printf "%s" "$${diff}"; \
271+
exit 1; \
272+
fi
273+
258274
.PHONY: checks
259275
checks: checks-frontend checks-backend ## run various consistency checks
260276

261277
.PHONY: checks-frontend
262278
checks-frontend: lockfile-check svg-check ## check frontend files
263279

264280
.PHONY: checks-backend
265-
checks-backend: tidy-check swagger-check fmt-check swagger-validate security-check ## check backend files
281+
checks-backend: tidy-check swagger-check openapi3-check fmt-check swagger-validate security-check ## check backend files
266282

267283
.PHONY: lint
268284
lint: lint-frontend lint-backend lint-spell ## lint everything

assets/go-licenses.json

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/generate-openapi.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2026 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
// generate-openapi converts Gitea's Swagger 2.0 spec into an OpenAPI 3.0 spec.
5+
//
6+
// Gitea generates a Swagger 2.0 spec from code annotations (make generate-swagger).
7+
// This tool converts it to OAS3 so that SDK generators and tools that require
8+
// OAS3 (e.g. progenitor for Rust) can consume it directly. The conversion also
9+
// deduplicates inline enum definitions into named schema components, producing
10+
// cleaner SDK output with proper enum types instead of anonymous strings.
11+
//
12+
// Run: go run build/generate-openapi.go
13+
// Output: templates/swagger/v1_openapi3_json.tmpl
14+
15+
//go:build ignore
16+
17+
package main
18+
19+
import (
20+
"encoding/json"
21+
"fmt"
22+
"log"
23+
"os"
24+
"regexp"
25+
"sort"
26+
"strings"
27+
28+
"code.gitea.io/gitea/build/openapi3gen"
29+
30+
"github.com/getkin/kin-openapi/openapi3"
31+
)
32+
33+
const (
34+
swaggerSpecPath = "templates/swagger/v1_json.tmpl"
35+
openapi3OutPath = "templates/swagger/v1_openapi3_json.tmpl"
36+
37+
appSubUrlVar = "{{.SwaggerAppSubUrl}}"
38+
appVerVar = "{{.SwaggerAppVer}}"
39+
40+
appSubUrlPlaceholder = "GITEA_APP_SUB_URL_PLACEHOLDER"
41+
appVerPlaceholder = "0.0.0-gitea-placeholder"
42+
)
43+
44+
var (
45+
appSubUrlRe = regexp.MustCompile(regexp.QuoteMeta(appSubUrlVar))
46+
appVerRe = regexp.MustCompile(regexp.QuoteMeta(appVerVar))
47+
48+
enumScanDirs = []string{
49+
"modules/structs",
50+
"modules/commitstatus",
51+
}
52+
)
53+
54+
func main() {
55+
astEnumMap, err := openapi3gen.ScanSwaggerEnumTypes(enumScanDirs)
56+
if err != nil {
57+
log.Fatalf("scanning swagger:enum annotations: %v", err)
58+
}
59+
names := make([]string, 0, len(astEnumMap))
60+
for _, n := range astEnumMap {
61+
names = append(names, n)
62+
}
63+
sort.Strings(names)
64+
fmt.Fprintf(os.Stderr, "discovered %d swagger:enum types: %s\n", len(names), strings.Join(names, ", "))
65+
66+
data, err := os.ReadFile(swaggerSpecPath)
67+
if err != nil {
68+
log.Fatalf("reading swagger spec: %v", err)
69+
}
70+
71+
cleaned := appSubUrlRe.ReplaceAll(data, []byte(appSubUrlPlaceholder))
72+
cleaned = appVerRe.ReplaceAll(cleaned, []byte(appVerPlaceholder))
73+
74+
oas3, err := openapi3gen.Convert(cleaned, astEnumMap)
75+
if err != nil {
76+
log.Fatalf("converting to openapi 3.0: %v", err)
77+
}
78+
79+
oas3.Servers = openapi3.Servers{
80+
{URL: appSubUrlPlaceholder + "/api/v1"},
81+
}
82+
83+
out, err := json.MarshalIndent(oas3, "", " ")
84+
if err != nil {
85+
log.Fatalf("marshaling openapi 3.0: %v", err)
86+
}
87+
88+
result := strings.ReplaceAll(string(out), appSubUrlPlaceholder, appSubUrlVar)
89+
result = strings.ReplaceAll(result, appVerPlaceholder, appVerVar)
90+
result = strings.TrimSpace(result)
91+
92+
if err := os.WriteFile(openapi3OutPath, []byte(result), 0o644); err != nil {
93+
log.Fatalf("writing openapi 3.0 spec: %v", err)
94+
}
95+
96+
fmt.Printf("Generated %s\n", openapi3OutPath)
97+
}

0 commit comments

Comments
 (0)