mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-04-26 13:06:57 -04:00
feat(api): merge Forgejo spec-first endpoints into unified OAS3 spec
Add a merge step that combines the auto-converted OAS3 spec (from Swagger 2.0) with the hand-written Forgejo API spec into a single unified OpenAPI 3.0 document. The merged spec uses /api as the server base URL, with /v1/... paths for the main API and /forgejo/v1/... paths for Forgejo-specific endpoints. Served at /openapi.v1.json.
This commit is contained in:
parent
7218b66a51
commit
3ceec0e5bb
6 changed files with 1066 additions and 409 deletions
|
|
@ -21,6 +21,9 @@ insert_final_newline = false
|
|||
[templates/swagger/v1_json.tmpl]
|
||||
indent_style = space
|
||||
|
||||
[templates/swagger/v1_openapi3_json.tmpl]
|
||||
indent_style = space
|
||||
|
||||
[templates/user/auth/oidc_wellknown.tmpl]
|
||||
indent_style = space
|
||||
|
||||
|
|
|
|||
7
Makefile
7
Makefile
|
|
@ -172,8 +172,8 @@ FORGEJO_API_SPEC := public/assets/forgejo/api.v1.yml
|
|||
|
||||
SWAGGER_SPEC := templates/swagger/v1_json.tmpl
|
||||
OPENAPI3_SPEC := templates/swagger/v1_openapi3_json.tmpl
|
||||
OPENAPI3_SPEC_S_TMPL := s|"url": *"/api/v1"|"url": "{{AppSubUrl \| JSEscape}}/api/v1"|g
|
||||
OPENAPI3_SPEC_S_JSON := s|"url": *"{{AppSubUrl \| JSEscape}}/api/v1"|"url": "/api/v1"|g
|
||||
OPENAPI3_SPEC_S_TMPL := s|"url": *"/api"|"url": "{{AppSubUrl \| JSEscape}}/api"|g
|
||||
OPENAPI3_SPEC_S_JSON := s|"url": *"{{AppSubUrl \| JSEscape}}/api"|"url": "/api"|g
|
||||
SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape}}/api/v1"|g
|
||||
SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape}}/api/v1"|"basePath": "/api/v1"|g
|
||||
SWAGGER_EXCLUDE := code.gitea.io/sdk
|
||||
|
|
@ -423,8 +423,9 @@ swagger-validate:
|
|||
.PHONY: generate-openapi3
|
||||
generate-openapi3: $(OPENAPI3_SPEC)
|
||||
|
||||
$(OPENAPI3_SPEC): $(SWAGGER_SPEC)
|
||||
$(OPENAPI3_SPEC): $(SWAGGER_SPEC) $(FORGEJO_API_SPEC)
|
||||
$(GO) run build/generate-openapi.go
|
||||
$(GO) run build/merge-openapi3.go
|
||||
|
||||
.PHONY: openapi3-check
|
||||
openapi3-check: generate-openapi3
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi2"
|
||||
|
|
@ -60,13 +61,26 @@ func main() {
|
|||
|
||||
// Ensure a servers entry exists with the base path including the placeholder
|
||||
oas3.Servers = openapi3.Servers{
|
||||
{URL: appSubUrlPlaceholder + "/api/v1"},
|
||||
{URL: appSubUrlPlaceholder + "/api"},
|
||||
}
|
||||
|
||||
// Prefix all paths with /v1 so the merged spec can use /api as the base
|
||||
prefixedPaths := make(openapi3.Paths, len(oas3.Paths))
|
||||
for path, item := range oas3.Paths {
|
||||
prefixedPaths["/v1"+path] = item
|
||||
}
|
||||
oas3.Paths = prefixedPaths
|
||||
|
||||
// Fix "type: file" schemas left over from Swagger 2.0 conversion.
|
||||
// In OAS3, file responses use type: string + format: binary.
|
||||
fixFileSchemas(oas3)
|
||||
|
||||
// OAS3 post-processing: enrich the spec with details that Swagger 2.0
|
||||
// and go-swagger cannot express.
|
||||
addURIFormats(oas3)
|
||||
addDeprecatedFlags(oas3)
|
||||
extractSharedEnums(oas3)
|
||||
|
||||
// Marshal to JSON with indentation
|
||||
out, err := json.MarshalIndent(oas3, "", " ")
|
||||
if err != nil {
|
||||
|
|
@ -126,3 +140,262 @@ func fixSchema(ref *openapi3.SchemaRef) {
|
|||
ref.Value.Format = "binary"
|
||||
}
|
||||
}
|
||||
|
||||
// addURIFormats sets format: uri on string properties whose names indicate they
|
||||
// hold URLs. This information is lost in Swagger 2.0 (go-swagger doesn't emit
|
||||
// format annotations for URL fields) but is valuable for code generators.
|
||||
func addURIFormats(doc *openapi3.T) {
|
||||
if doc.Components == nil {
|
||||
return
|
||||
}
|
||||
for _, schemaRef := range doc.Components.Schemas {
|
||||
if schemaRef.Value == nil {
|
||||
continue
|
||||
}
|
||||
for propName, propRef := range schemaRef.Value.Properties {
|
||||
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||
continue
|
||||
}
|
||||
prop := propRef.Value
|
||||
if prop.Type != "string" || prop.Format != "" {
|
||||
continue
|
||||
}
|
||||
if isURLProperty(propName) {
|
||||
prop.Format = "uri"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isURLProperty returns true if the property name indicates it holds a URL.
|
||||
func isURLProperty(name string) bool {
|
||||
if strings.HasSuffix(name, "_url") {
|
||||
return true
|
||||
}
|
||||
switch name {
|
||||
case "url", "html_url", "clone_url":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// addDeprecatedFlags sets deprecated: true on schema properties whose
|
||||
// description contains "deprecated". In OAS3, deprecated is a first-class
|
||||
// boolean; Swagger 2.0 could only express it as text in the description.
|
||||
func addDeprecatedFlags(doc *openapi3.T) {
|
||||
if doc.Components == nil {
|
||||
return
|
||||
}
|
||||
for _, schemaRef := range doc.Components.Schemas {
|
||||
if schemaRef.Value == nil {
|
||||
continue
|
||||
}
|
||||
for _, propRef := range schemaRef.Value.Properties {
|
||||
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||
continue
|
||||
}
|
||||
desc := strings.ToLower(propRef.Value.Description)
|
||||
if strings.Contains(desc, "deprecated") {
|
||||
propRef.Value.Deprecated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type enumUsage struct {
|
||||
schemaName string
|
||||
propName string
|
||||
propRef *openapi3.SchemaRef
|
||||
inItems bool // true if enum is on .Items, not the prop itself
|
||||
}
|
||||
|
||||
// extractSharedEnums finds identical enum arrays used by multiple schema
|
||||
// properties, creates a standalone named schema for each, and replaces the
|
||||
// inline enums with $ref pointers. This produces proper enum types for code
|
||||
// generators instead of anonymous inline enums repeated on each field.
|
||||
func extractSharedEnums(doc *openapi3.T) {
|
||||
if doc.Components == nil {
|
||||
return
|
||||
}
|
||||
|
||||
enumGroups := map[string][]enumUsage{}
|
||||
|
||||
for schemaName, schemaRef := range doc.Components.Schemas {
|
||||
if schemaRef.Value == nil {
|
||||
continue
|
||||
}
|
||||
for propName, propRef := range schemaRef.Value.Properties {
|
||||
if propRef == nil || propRef.Value == nil || propRef.Ref != "" {
|
||||
continue
|
||||
}
|
||||
if len(propRef.Value.Enum) > 1 && propRef.Value.Type == "string" {
|
||||
key := enumKey(propRef.Value.Enum)
|
||||
enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, false})
|
||||
}
|
||||
// Check array items
|
||||
if propRef.Value.Type == "array" && propRef.Value.Items != nil &&
|
||||
propRef.Value.Items.Value != nil && propRef.Value.Items.Ref == "" &&
|
||||
len(propRef.Value.Items.Value.Enum) > 1 && propRef.Value.Items.Value.Type == "string" {
|
||||
key := enumKey(propRef.Value.Items.Value.Enum)
|
||||
enumGroups[key] = append(enumGroups[key], enumUsage{schemaName, propName, propRef, true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only extract enums used by 2+ fields
|
||||
for key, usages := range enumGroups {
|
||||
if len(usages) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Derive a name from the enum values and usage context
|
||||
enumName := deriveEnumName(usages)
|
||||
// Avoid collisions with existing schemas
|
||||
if _, exists := doc.Components.Schemas[enumName]; exists {
|
||||
enumName += "Type"
|
||||
}
|
||||
if _, exists := doc.Components.Schemas[enumName]; exists {
|
||||
continue // still collides, skip
|
||||
}
|
||||
|
||||
// Get the enum values from the first usage
|
||||
var enumValues []any
|
||||
if usages[0].inItems {
|
||||
enumValues = usages[0].propRef.Value.Items.Value.Enum
|
||||
} else {
|
||||
enumValues = usages[0].propRef.Value.Enum
|
||||
}
|
||||
|
||||
// Create the standalone enum schema
|
||||
doc.Components.Schemas[enumName] = &openapi3.SchemaRef{
|
||||
Value: &openapi3.Schema{
|
||||
Type: "string",
|
||||
Enum: enumValues,
|
||||
},
|
||||
}
|
||||
|
||||
ref := "#/components/schemas/" + enumName
|
||||
|
||||
// Replace inline enums with $ref
|
||||
for _, usage := range usages {
|
||||
if usage.inItems {
|
||||
usage.propRef.Value.Items = &openapi3.SchemaRef{Ref: ref}
|
||||
} else {
|
||||
// Preserve the property description and other metadata by
|
||||
// wrapping in allOf: the $ref provides the type+enum, the
|
||||
// inline schema provides description/deprecated/etc.
|
||||
old := usage.propRef.Value
|
||||
if old.Description == "" && !old.Deprecated && old.Format == "" {
|
||||
// Simple case: just replace with a $ref
|
||||
usage.propRef.Ref = ref
|
||||
usage.propRef.Value = nil
|
||||
} else {
|
||||
// Has metadata: use allOf to combine $ref with metadata
|
||||
usage.propRef.Value = &openapi3.Schema{
|
||||
AllOf: openapi3.SchemaRefs{
|
||||
{Ref: ref},
|
||||
},
|
||||
Description: old.Description,
|
||||
Deprecated: old.Deprecated,
|
||||
}
|
||||
// Clear enum from the wrapper (it's in the $ref now)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = key // used as map key
|
||||
}
|
||||
}
|
||||
|
||||
// enumKey returns a canonical string key for an enum value set, for grouping.
|
||||
func enumKey(values []any) string {
|
||||
strs := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
strs[i] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
sort.Strings(strs)
|
||||
return strings.Join(strs, "|")
|
||||
}
|
||||
|
||||
// Known Go type names for enum types. These are not present in the Swagger 2.0
|
||||
// definitions (go-swagger doesn't emit standalone string type definitions), so
|
||||
// we map them explicitly using the const name prefix from x-go-enum-desc.
|
||||
var knownEnumTypes = map[string]string{
|
||||
"CommitStatus": "CommitStatusState",
|
||||
"State": "StateType",
|
||||
"ReviewState": "ReviewStateType",
|
||||
"NotifySubject": "NotifySubjectType",
|
||||
"IssueFormField": "IssueFormFieldType",
|
||||
"ObjectFormatName": "ObjectFormatName",
|
||||
}
|
||||
|
||||
// deriveEnumName picks a name for an extracted enum schema based on the
|
||||
// Go const name prefix from x-go-enum-desc, with a fallback to the property name.
|
||||
func deriveEnumName(usages []enumUsage) string {
|
||||
// Try to extract the Go type name from x-go-enum-desc.
|
||||
// Format: "value ConstName ConstName description\n..."
|
||||
// e.g. "pending CommitStatusPending CommitStatusPending is for..."
|
||||
for _, u := range usages {
|
||||
if u.propRef.Value == nil {
|
||||
continue
|
||||
}
|
||||
desc, ok := u.propRef.Value.Extensions["x-go-enum-desc"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
s, ok := desc.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(s)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
constName := parts[1]
|
||||
|
||||
// Try to strip the enum value from the const name to get the prefix
|
||||
var vals []any
|
||||
if u.inItems {
|
||||
vals = u.propRef.Value.Items.Value.Enum
|
||||
} else {
|
||||
vals = u.propRef.Value.Enum
|
||||
}
|
||||
for _, v := range vals {
|
||||
vs := fmt.Sprintf("%v", v)
|
||||
// Case-insensitive suffix matching: the enum value may be
|
||||
// lowercase ("pending"), UPPER ("APPROVED"), or mixed ("sha1"),
|
||||
// while the Go const uses PascalCase ("Pending", "Approved", "SHA1").
|
||||
lowerConst := strings.ToLower(constName)
|
||||
lowerVal := strings.ToLower(vs)
|
||||
if strings.HasSuffix(lowerConst, lowerVal) && len(lowerVal) < len(lowerConst) {
|
||||
prefix := constName[:len(constName)-len(vs)]
|
||||
// Check if we have a known Go type name for this prefix
|
||||
if goType, ok := knownEnumTypes[prefix]; ok {
|
||||
return goType
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: use the most common property name, PascalCased
|
||||
nameCounts := map[string]int{}
|
||||
for _, u := range usages {
|
||||
nameCounts[u.propName]++
|
||||
}
|
||||
bestName := ""
|
||||
bestCount := 0
|
||||
for name, count := range nameCounts {
|
||||
if count > bestCount || (count == bestCount && name < bestName) {
|
||||
bestName = name
|
||||
bestCount = count
|
||||
}
|
||||
}
|
||||
result := ""
|
||||
for _, p := range strings.Split(bestName, "_") {
|
||||
if len(p) > 0 {
|
||||
result += strings.ToUpper(p[:1]) + p[1:]
|
||||
}
|
||||
}
|
||||
return result + "Enum"
|
||||
}
|
||||
|
|
|
|||
227
build/merge-openapi3.go
Normal file
227
build/merge-openapi3.go
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
const (
|
||||
openapi3SpecPath = "templates/swagger/v1_openapi3_json.tmpl"
|
||||
forgejoSpecPath = "public/assets/forgejo/api.v1.yml"
|
||||
|
||||
appSubUrlVar = "{{AppSubUrl | JSEscape}}"
|
||||
appVerVar = "{{AppVer | JSEscape}}"
|
||||
|
||||
appSubUrlPlaceholder = "FORGEJO_APP_SUB_URL_PLACEHOLDER"
|
||||
appVerPlaceholder = "0.0.0-forgejo-placeholder"
|
||||
)
|
||||
|
||||
var (
|
||||
appSubUrlRe = regexp.MustCompile(regexp.QuoteMeta(appSubUrlVar))
|
||||
appVerRe = regexp.MustCompile(regexp.QuoteMeta(appVerVar))
|
||||
)
|
||||
|
||||
func main() {
|
||||
// --- Load the auto-converted OAS3 spec (from generate-openapi.go output) ---
|
||||
data, err := os.ReadFile(openapi3SpecPath)
|
||||
if err != nil {
|
||||
log.Fatalf("reading openapi3 spec: %v", err)
|
||||
}
|
||||
|
||||
// Replace Go template variables with placeholders so we have valid JSON for parsing
|
||||
cleaned := appSubUrlRe.ReplaceAll(data, []byte(appSubUrlPlaceholder))
|
||||
cleaned = appVerRe.ReplaceAll(cleaned, []byte(appVerPlaceholder))
|
||||
|
||||
baseDoc, err := openapi3.NewLoader().LoadFromData(cleaned)
|
||||
if err != nil {
|
||||
log.Fatalf("parsing openapi3 spec: %v", err)
|
||||
}
|
||||
|
||||
// --- Load the hand-written Forgejo spec ---
|
||||
forgejoDoc, err := openapi3.NewLoader().LoadFromFile(forgejoSpecPath)
|
||||
if err != nil {
|
||||
log.Fatalf("parsing forgejo spec: %v", err)
|
||||
}
|
||||
|
||||
// --- Collect existing operationIds from the base spec ---
|
||||
existingOpIDs := make(map[string]bool)
|
||||
for _, item := range baseDoc.Paths {
|
||||
for _, op := range []*openapi3.Operation{
|
||||
item.Get, item.Post, item.Put, item.Patch,
|
||||
item.Delete, item.Head, item.Options, item.Trace,
|
||||
} {
|
||||
if op != nil && op.OperationID != "" {
|
||||
existingOpIDs[op.OperationID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Merge Forgejo paths into the base spec ---
|
||||
// Forgejo spec paths are under /api/forgejo/v1; we prefix them with /forgejo/v1
|
||||
// (the base spec already uses /api as the server URL).
|
||||
// Prefix conflicting operationIds with "forgejo" to avoid duplicates.
|
||||
for path, item := range forgejoDoc.Paths {
|
||||
for _, op := range []*openapi3.Operation{
|
||||
item.Get, item.Post, item.Put, item.Patch,
|
||||
item.Delete, item.Head, item.Options, item.Trace,
|
||||
} {
|
||||
if op != nil && op.OperationID != "" && existingOpIDs[op.OperationID] {
|
||||
op.OperationID = "forgejo" + strings.ToUpper(op.OperationID[:1]) + op.OperationID[1:]
|
||||
}
|
||||
}
|
||||
mergedPath := "/forgejo/v1" + path
|
||||
baseDoc.Paths[mergedPath] = item
|
||||
}
|
||||
|
||||
// --- Merge Forgejo schemas into the base spec ---
|
||||
if forgejoDoc.Components != nil && forgejoDoc.Components.Schemas != nil {
|
||||
if baseDoc.Components == nil {
|
||||
baseDoc.Components = &openapi3.Components{}
|
||||
}
|
||||
if baseDoc.Components.Schemas == nil {
|
||||
baseDoc.Components.Schemas = make(openapi3.Schemas)
|
||||
}
|
||||
|
||||
// Build a set of schema names that need renaming (conflicts)
|
||||
renames := make(map[string]string)
|
||||
for name := range forgejoDoc.Components.Schemas {
|
||||
if _, exists := baseDoc.Components.Schemas[name]; exists {
|
||||
renames[name] = "Forgejo" + name
|
||||
}
|
||||
}
|
||||
|
||||
// Add Forgejo schemas (renaming conflicts)
|
||||
for name, schema := range forgejoDoc.Components.Schemas {
|
||||
targetName := name
|
||||
if renamed, ok := renames[name]; ok {
|
||||
targetName = renamed
|
||||
}
|
||||
baseDoc.Components.Schemas[targetName] = schema
|
||||
}
|
||||
|
||||
// Update $ref pointers in Forgejo paths to use renamed schemas
|
||||
if len(renames) > 0 {
|
||||
updateRefs(forgejoDoc, renames, baseDoc)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Merge Forgejo parameters into the base spec ---
|
||||
if forgejoDoc.Components != nil && forgejoDoc.Components.Parameters != nil {
|
||||
if baseDoc.Components.Parameters == nil {
|
||||
baseDoc.Components.Parameters = make(openapi3.ParametersMap)
|
||||
}
|
||||
for name, param := range forgejoDoc.Components.Parameters {
|
||||
baseDoc.Components.Parameters[name] = param
|
||||
}
|
||||
}
|
||||
|
||||
// --- Merge Forgejo responses into the base spec ---
|
||||
if forgejoDoc.Components != nil && forgejoDoc.Components.Responses != nil {
|
||||
if baseDoc.Components.Responses == nil {
|
||||
baseDoc.Components.Responses = make(openapi3.Responses)
|
||||
}
|
||||
for name, resp := range forgejoDoc.Components.Responses {
|
||||
baseDoc.Components.Responses[name] = resp
|
||||
}
|
||||
}
|
||||
|
||||
// --- Marshal and re-inject template variables ---
|
||||
out, err := json.MarshalIndent(baseDoc, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("marshaling merged spec: %v", err)
|
||||
}
|
||||
|
||||
result := strings.ReplaceAll(string(out), appSubUrlPlaceholder, appSubUrlVar)
|
||||
result = strings.ReplaceAll(result, appVerPlaceholder, appVerVar)
|
||||
|
||||
if !strings.HasSuffix(result, "\n") {
|
||||
result += "\n"
|
||||
}
|
||||
|
||||
if err := os.WriteFile(openapi3SpecPath, []byte(result), 0o644); err != nil {
|
||||
log.Fatalf("writing merged spec: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Merged Forgejo spec into %s\n", openapi3SpecPath)
|
||||
}
|
||||
|
||||
// updateRefs rewrites $ref pointers in the Forgejo paths (now merged into baseDoc)
|
||||
// to account for renamed schemas.
|
||||
func updateRefs(forgejoDoc *openapi3.T, renames map[string]string, baseDoc *openapi3.T) {
|
||||
// Only need to fix refs in the Forgejo-originated paths
|
||||
for path := range forgejoDoc.Paths {
|
||||
mergedPath := "/forgejo/v1" + path
|
||||
item := baseDoc.Paths[mergedPath]
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
for _, op := range []*openapi3.Operation{
|
||||
item.Get, item.Post, item.Put, item.Patch,
|
||||
item.Delete, item.Head, item.Options, item.Trace,
|
||||
} {
|
||||
if op == nil {
|
||||
continue
|
||||
}
|
||||
// Fix response schema refs
|
||||
for _, resp := range op.Responses {
|
||||
if resp.Value == nil {
|
||||
continue
|
||||
}
|
||||
for _, mt := range resp.Value.Content {
|
||||
fixSchemaRef(mt.Schema, renames)
|
||||
}
|
||||
}
|
||||
// Fix request body schema refs
|
||||
if op.RequestBody != nil && op.RequestBody.Value != nil {
|
||||
for _, mt := range op.RequestBody.Value.Content {
|
||||
fixSchemaRef(mt.Schema, renames)
|
||||
}
|
||||
}
|
||||
// Fix parameter schema refs
|
||||
for _, param := range op.Parameters {
|
||||
if param.Value != nil && param.Value.Schema != nil {
|
||||
fixSchemaRef(param.Value.Schema, renames)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fixSchemaRef rewrites a $ref if it points to a renamed schema.
|
||||
func fixSchemaRef(ref *openapi3.SchemaRef, renames map[string]string) {
|
||||
if ref == nil {
|
||||
return
|
||||
}
|
||||
if ref.Ref != "" {
|
||||
for oldName, newName := range renames {
|
||||
oldRef := "#/components/schemas/" + oldName
|
||||
if ref.Ref == oldRef {
|
||||
ref.Ref = "#/components/schemas/" + newName
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Recurse into inline schemas
|
||||
if ref.Value != nil {
|
||||
if ref.Value.Items != nil {
|
||||
fixSchemaRef(ref.Value.Items, renames)
|
||||
}
|
||||
for _, prop := range ref.Value.Properties {
|
||||
fixSchemaRef(prop, renames)
|
||||
}
|
||||
if ref.Value.AdditionalProperties.Schema != nil {
|
||||
fixSchemaRef(ref.Value.AdditionalProperties.Schema, renames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,4 +10,5 @@ package build
|
|||
import (
|
||||
_ "github.com/getkin/kin-openapi/openapi2"
|
||||
_ "github.com/getkin/kin-openapi/openapi2conv"
|
||||
_ "github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue