feat(openapi): sort generated tags deterministically
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
c21c3409d7
commit
e23d8e9780
2 changed files with 80 additions and 0 deletions
25
openapi.go
25
openapi.go
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"iter"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
|
@ -609,9 +610,33 @@ func (sb *SpecBuilder) buildTags(groups []preparedRouteGroup) []map[string]any {
|
|||
}
|
||||
}
|
||||
|
||||
sortTags(tags)
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
// sortTags keeps system first and orders the remaining tags alphabetically so
|
||||
// generated specs stay stable across registration order changes.
|
||||
func sortTags(tags []map[string]any) {
|
||||
if len(tags) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
sort.SliceStable(tags, func(i, j int) bool {
|
||||
left, _ := tags[i]["name"].(string)
|
||||
right, _ := tags[j]["name"].(string)
|
||||
|
||||
switch {
|
||||
case left == "system":
|
||||
return true
|
||||
case right == "system":
|
||||
return false
|
||||
default:
|
||||
return left < right
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func graphqlPathItem(path string, operationIDs map[string]int) map[string]any {
|
||||
return map[string]any{
|
||||
"post": map[string]any{
|
||||
|
|
|
|||
|
|
@ -1554,6 +1554,61 @@ func TestSpecBuilder_Good_DefaultTagsFromGroupName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_TagsAreSortedDeterministically(t *testing.T) {
|
||||
sb := &api.SpecBuilder{
|
||||
Title: "Test",
|
||||
Version: "1.0.0",
|
||||
}
|
||||
|
||||
group := &specStubGroup{
|
||||
name: "gamma",
|
||||
basePath: "/api/gamma",
|
||||
descs: []api.RouteDescription{
|
||||
{
|
||||
Method: "GET",
|
||||
Path: "/status",
|
||||
Summary: "Check status",
|
||||
Tags: []string{"zeta", "alpha", "beta"},
|
||||
Response: map[string]any{
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := sb.Build([]api.RouteGroup{group})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var spec map[string]any
|
||||
if err := json.Unmarshal(data, &spec); err != nil {
|
||||
t.Fatalf("invalid JSON: %v", err)
|
||||
}
|
||||
|
||||
tags, ok := spec["tags"].([]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected tags array, got %T", spec["tags"])
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(tags))
|
||||
for _, raw := range tags {
|
||||
tag := raw.(map[string]any)
|
||||
name, _ := tag["name"].(string)
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
expected := []string{"system", "alpha", "beta", "gamma", "zeta"}
|
||||
if len(names) != len(expected) {
|
||||
t.Fatalf("expected %d tags, got %d: %v", len(expected), len(names), names)
|
||||
}
|
||||
for i := range expected {
|
||||
if names[i] != expected[i] {
|
||||
t.Fatalf("expected tag order %v, got %v", expected, names)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_DeprecatedOperation(t *testing.T) {
|
||||
sb := &api.SpecBuilder{
|
||||
Title: "Test",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue