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>
This commit is contained in:
Myers Carpenter
2026-04-29 08:47:52 -04:00
committed by GitHub
parent 18762c7748
commit 9e031eb3df
39 changed files with 34700 additions and 99 deletions

View File

@@ -48,7 +48,7 @@ func CreateOrg(ctx *context.APIContext) {
visibility := api.VisibleTypePublic
if form.Visibility != "" {
visibility = api.VisibilityModes[form.Visibility]
visibility = api.VisibilityModes[string(form.Visibility)]
}
org := &organization.Organization{

View File

@@ -123,7 +123,7 @@ func CreateUser(ctx *context.APIContext) {
}
if form.Visibility != "" {
visibility := api.VisibilityModes[form.Visibility]
visibility := api.VisibilityModes[string(form.Visibility)]
overwriteDefault.Visibility = &visibility
}
@@ -239,7 +239,7 @@ func EditUser(ctx *context.APIContext) {
Description: optional.FromPtr(form.Description),
IsActive: optional.FromPtr(form.Active),
IsAdmin: user_service.UpdateOptionFieldFromPtr(form.Admin),
Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility),
Visibility: optional.FromMapLookup(api.VisibilityModes, string(form.Visibility)),
AllowGitHook: optional.FromPtr(form.AllowGitHook),
AllowImportLocal: optional.FromPtr(form.AllowImportLocal),
MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),

View File

@@ -258,7 +258,7 @@ func Create(ctx *context.APIContext) {
visibility := api.VisibleTypePublic
if form.Visibility != "" {
visibility = api.VisibilityModes[form.Visibility]
visibility = api.VisibilityModes[string(form.Visibility)]
}
org := &organization.Organization{
@@ -402,7 +402,7 @@ func Edit(ctx *context.APIContext) {
Description: optional.FromPtr(form.Description),
Website: optional.FromPtr(form.Website),
Location: optional.FromPtr(form.Location),
Visibility: optional.FromMapLookup(api.VisibilityModes, optional.FromPtr(form.Visibility).Value()),
Visibility: optional.FromMapLookup(api.VisibilityModes, string(optional.FromPtr(form.Visibility).Value())),
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
}
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {

View File

@@ -210,7 +210,7 @@ func CreateTeam(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateTeamOption)
teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin)
teamPermission := perm.ParseAccessMode(string(form.Permission), perm.AccessModeNone, perm.AccessModeAdmin)
team := &organization.Team{
OrgID: ctx.Org.Organization.ID,
Name: form.Name,
@@ -224,7 +224,7 @@ func CreateTeam(ctx *context.APIContext) {
if len(form.UnitsMap) > 0 {
attachTeamUnitsMap(team, form.UnitsMap)
} else if len(form.Units) > 0 {
unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite)
unitPerm := perm.ParseAccessMode(string(form.Permission), perm.AccessModeRead, perm.AccessModeWrite)
attachTeamUnits(team, unitPerm, form.Units)
} else {
ctx.APIErrorInternal(errors.New("units permission should not be empty"))
@@ -298,7 +298,7 @@ func EditTeam(ctx *context.APIContext) {
isAuthChanged := false
isIncludeAllChanged := false
if !team.IsOwnerTeam() && len(form.Permission) != 0 {
teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin)
teamPermission := perm.ParseAccessMode(string(form.Permission), perm.AccessModeNone, perm.AccessModeAdmin)
if team.AccessMode != teamPermission {
isAuthChanged = true
team.AccessMode = teamPermission
@@ -314,7 +314,7 @@ func EditTeam(ctx *context.APIContext) {
if len(form.UnitsMap) > 0 {
attachTeamUnitsMap(team, form.UnitsMap)
} else if len(form.Units) > 0 {
unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite)
unitPerm := perm.ParseAccessMode(string(form.Permission), perm.AccessModeRead, perm.AccessModeWrite)
attachTeamUnits(team, unitPerm, form.Units)
}
} else {

View File

@@ -181,7 +181,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) {
p := perm.AccessModeWrite
if form.Permission != nil {
p = perm.ParseAccessMode(*form.Permission, perm.AccessModeRead, perm.AccessModeWrite, perm.AccessModeAdmin)
p = perm.ParseAccessMode(string(*form.Permission), perm.AccessModeRead, perm.AccessModeWrite, perm.AccessModeAdmin)
}
if err := repo_service.AddOrUpdateCollaborator(ctx, ctx.Repo.Repository, collaborator, p); err != nil {

View File

@@ -262,7 +262,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
DefaultBranch: opt.DefaultBranch,
TrustModel: repo_model.ToTrustModel(opt.TrustModel),
IsTemplate: opt.Template,
ObjectFormatName: opt.ObjectFormatName,
ObjectFormatName: string(opt.ObjectFormatName),
})
if err != nil {
if repo_model.IsErrRepoAlreadyExist(err) {

View File

@@ -16,3 +16,10 @@ func SwaggerV1Json(ctx *context.Context) {
ctx.Data["SwaggerAppSubUrl"] = setting.AppSubURL // it is JS-safe
ctx.JSONTemplate("swagger/v1_json")
}
// OpenAPI3Json render OpenAPI 3.0 json (auto-converted from Swagger 2.0)
func OpenAPI3Json(ctx *context.Context) {
ctx.Data["SwaggerAppVer"] = template.HTML(template.JSEscapeString(setting.AppVer))
ctx.Data["SwaggerAppSubUrl"] = setting.AppSubURL // it is JS-safe
ctx.JSONTemplate("swagger/v1_openapi3_json")
}

View File

@@ -1751,6 +1751,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
if setting.API.EnableSwagger {
m.Get("/swagger.v1.json", SwaggerV1Json)
m.Get("/openapi3.v1.json", OpenAPI3Json)
}
if !setting.IsProd || setting.IsInE2eTesting() {