87 lines
2.5 KiB
Go
87 lines
2.5 KiB
Go
package apigen
|
|
|
|
import "encoding/json"
|
|
|
|
// compactDocument only replaces standard OpenAPI Reference Object positions.
|
|
// Payloads (including examples containing a literal "$ref") are never walked.
|
|
// Mixed HTTP 200 responses retain both success and error alternatives.
|
|
func compactDocument(doc map[string]any) {
|
|
type slot struct {
|
|
parent map[string]any
|
|
key string
|
|
}
|
|
components := doc["components"].(map[string]any)
|
|
var responses, schemas, examples []slot
|
|
for _, path := range doc["paths"].(map[string]any) {
|
|
for _, value := range path.(map[string]any) {
|
|
op, ok := value.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
rs, _ := op["responses"].(map[string]any)
|
|
for status := range rs {
|
|
responses = append(responses, slot{rs, status})
|
|
|
|
}
|
|
}
|
|
}
|
|
factor := func(kind string, slots []slot) {
|
|
counts := map[string]int{}
|
|
for _, s := range slots {
|
|
raw, _ := json.Marshal(s.parent[s.key])
|
|
counts[string(raw)]++
|
|
}
|
|
shared, _ := components[kind].(map[string]any)
|
|
if shared == nil {
|
|
shared = map[string]any{}
|
|
}
|
|
for _, s := range slots {
|
|
raw, _ := json.Marshal(s.parent[s.key])
|
|
count := counts[string(raw)]
|
|
if count < 2 {
|
|
continue
|
|
}
|
|
name := "Omni_" + digest(raw)
|
|
ref := map[string]any{"$ref": "#/components/" + kind + "/" + name}
|
|
refJSON, _ := json.Marshal(ref)
|
|
// Keep small objects inline if components would increase size.
|
|
if (count-1)*len(raw) <= count*len(refJSON)+len(name)+4 {
|
|
continue
|
|
}
|
|
shared[name] = s.parent[s.key]
|
|
s.parent[s.key] = map[string]any{"$ref": "#/components/" + kind + "/" + name}
|
|
}
|
|
if len(shared) > 0 {
|
|
components[kind] = shared
|
|
}
|
|
}
|
|
|
|
// Share whole responses before counting inner objects. Otherwise a catalog
|
|
// copied across 100 routes can create hundreds of unnecessary components
|
|
// that only one shared response ultimately uses.
|
|
factor("responses", responses)
|
|
collectMedia := func(value any) {
|
|
resp := value.(map[string]any)
|
|
content, _ := resp["content"].(map[string]any)
|
|
for _, value := range content {
|
|
media := value.(map[string]any)
|
|
if media["schema"] != nil {
|
|
schemas = append(schemas, slot{media, "schema"})
|
|
}
|
|
es, _ := media["examples"].(map[string]any)
|
|
for name := range es {
|
|
examples = append(examples, slot{es, name})
|
|
}
|
|
}
|
|
}
|
|
for _, s := range responses {
|
|
collectMedia(s.parent[s.key])
|
|
}
|
|
sharedResponses, _ := components["responses"].(map[string]any)
|
|
for _, resp := range sharedResponses {
|
|
collectMedia(resp)
|
|
}
|
|
factor("examples", examples)
|
|
factor("schemas", schemas)
|
|
}
|