feat(api): add openapi info summary support
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
be7616d437
commit
d225fd3178
10 changed files with 104 additions and 3 deletions
1
api.go
1
api.go
|
|
@ -42,6 +42,7 @@ type Engine struct {
|
|||
sseBroker *SSEBroker
|
||||
swaggerEnabled bool
|
||||
swaggerTitle string
|
||||
swaggerSummary string
|
||||
swaggerDesc string
|
||||
swaggerVersion string
|
||||
swaggerPath string
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ func addSDKCommand(parent *cli.Command) {
|
|||
specFile string
|
||||
packageName string
|
||||
title string
|
||||
summary string
|
||||
description string
|
||||
version string
|
||||
swaggerPath string
|
||||
|
|
@ -58,7 +59,7 @@ func addSDKCommand(parent *cli.Command) {
|
|||
|
||||
// If no spec file provided, generate one to a temp file.
|
||||
if specFile == "" {
|
||||
builder, err := sdkSpecBuilder(title, description, version, swaggerPath, graphqlPath, graphqlPlayground, ssePath, wsPath, pprofEnabled, expvarEnabled, termsURL, contactName, contactURL, contactEmail, licenseName, licenseURL, externalDocsDescription, externalDocsURL, servers, securitySchemes)
|
||||
builder, err := sdkSpecBuilder(title, summary, description, version, swaggerPath, graphqlPath, graphqlPlayground, ssePath, wsPath, pprofEnabled, expvarEnabled, termsURL, contactName, contactURL, contactEmail, licenseName, licenseURL, externalDocsDescription, externalDocsURL, servers, securitySchemes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -111,6 +112,7 @@ func addSDKCommand(parent *cli.Command) {
|
|||
cli.StringFlag(cmd, &specFile, "spec", "s", "", "Path to an existing OpenAPI spec (generates a temporary spec from registered route groups and the built-in tool bridge if not provided)")
|
||||
cli.StringFlag(cmd, &packageName, "package", "p", "lethean", "Package name for generated SDK")
|
||||
cli.StringFlag(cmd, &title, "title", "t", defaultSDKTitle, "API title in generated spec")
|
||||
cli.StringFlag(cmd, &summary, "summary", "", "", "OpenAPI info summary in generated spec")
|
||||
cli.StringFlag(cmd, &description, "description", "d", defaultSDKDescription, "API description in generated spec")
|
||||
cli.StringFlag(cmd, &version, "version", "V", defaultSDKVersion, "API version in generated spec")
|
||||
cli.StringFlag(cmd, &swaggerPath, "swagger-path", "", "", "Swagger UI path in generated spec")
|
||||
|
|
@ -134,9 +136,10 @@ func addSDKCommand(parent *cli.Command) {
|
|||
parent.AddCommand(cmd)
|
||||
}
|
||||
|
||||
func sdkSpecBuilder(title, description, version, swaggerPath, graphqlPath string, graphqlPlayground bool, ssePath, wsPath string, pprofEnabled, expvarEnabled bool, termsURL, contactName, contactURL, contactEmail, licenseName, licenseURL, externalDocsDescription, externalDocsURL, servers, securitySchemes string) (*goapi.SpecBuilder, error) {
|
||||
func sdkSpecBuilder(title, summary, description, version, swaggerPath, graphqlPath string, graphqlPlayground bool, ssePath, wsPath string, pprofEnabled, expvarEnabled bool, termsURL, contactName, contactURL, contactEmail, licenseName, licenseURL, externalDocsDescription, externalDocsURL, servers, securitySchemes string) (*goapi.SpecBuilder, error) {
|
||||
return newSpecBuilder(specBuilderConfig{
|
||||
title: title,
|
||||
summary: summary,
|
||||
description: description,
|
||||
version: version,
|
||||
swaggerPath: swaggerPath,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ func addSpecCommand(parent *cli.Command) {
|
|||
output string
|
||||
format string
|
||||
title string
|
||||
summary string
|
||||
description string
|
||||
version string
|
||||
swaggerPath string
|
||||
|
|
@ -43,6 +44,7 @@ func addSpecCommand(parent *cli.Command) {
|
|||
// Build spec from all route groups registered for CLI generation.
|
||||
builder, err := newSpecBuilder(specBuilderConfig{
|
||||
title: title,
|
||||
summary: summary,
|
||||
description: description,
|
||||
version: version,
|
||||
swaggerPath: swaggerPath,
|
||||
|
|
@ -84,6 +86,7 @@ func addSpecCommand(parent *cli.Command) {
|
|||
cli.StringFlag(cmd, &output, "output", "o", "", "Write spec to file instead of stdout")
|
||||
cli.StringFlag(cmd, &format, "format", "f", "json", "Output format: json or yaml")
|
||||
cli.StringFlag(cmd, &title, "title", "t", "Lethean Core API", "API title in spec")
|
||||
cli.StringFlag(cmd, &summary, "summary", "", "", "OpenAPI info summary in spec")
|
||||
cli.StringFlag(cmd, &description, "description", "d", "Lethean Core API", "API description in spec")
|
||||
cli.StringFlag(cmd, &version, "version", "V", "1.0.0", "API version in spec")
|
||||
cli.StringFlag(cmd, &swaggerPath, "swagger-path", "", "", "Swagger UI path in generated spec")
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ func TestAPISpecCmd_Good_JSON(t *testing.T) {
|
|||
if specCmd.Flag("title") == nil {
|
||||
t.Fatal("expected --title flag on spec command")
|
||||
}
|
||||
if specCmd.Flag("summary") == nil {
|
||||
t.Fatal("expected --summary flag on spec command")
|
||||
}
|
||||
if specCmd.Flag("description") == nil {
|
||||
t.Fatal("expected --description flag on spec command")
|
||||
}
|
||||
|
|
@ -178,6 +181,41 @@ func TestAPISpecCmd_Good_CustomDescription(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAPISpecCmd_Good_SummaryPopulatesSpecInfo(t *testing.T) {
|
||||
root := &cli.Command{Use: "root"}
|
||||
AddAPICommands(root)
|
||||
|
||||
outputFile := t.TempDir() + "/spec.json"
|
||||
root.SetArgs([]string{
|
||||
"api", "spec",
|
||||
"--summary", "Short API overview",
|
||||
"--output", outputFile,
|
||||
})
|
||||
root.SetErr(new(bytes.Buffer))
|
||||
|
||||
if err := root.Execute(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(outputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("expected spec file to be written: %v", err)
|
||||
}
|
||||
|
||||
var spec map[string]any
|
||||
if err := json.Unmarshal(data, &spec); err != nil {
|
||||
t.Fatalf("expected valid JSON spec, got error: %v", err)
|
||||
}
|
||||
|
||||
info, ok := spec["info"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("expected info object in generated spec")
|
||||
}
|
||||
if info["summary"] != "Short API overview" {
|
||||
t.Fatalf("expected summary to be preserved, got %v", info["summary"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPISpecCmd_Good_GraphQLPlaygroundFlagPopulatesSpecPaths(t *testing.T) {
|
||||
root := &cli.Command{Use: "root"}
|
||||
AddAPICommands(root)
|
||||
|
|
@ -758,6 +796,7 @@ func TestAPISDKCmd_Good_TempSpecUsesMetadataFlags(t *testing.T) {
|
|||
|
||||
builder, err := sdkSpecBuilder(
|
||||
"Custom SDK API",
|
||||
"Custom SDK overview",
|
||||
"Custom SDK description",
|
||||
"9.9.9",
|
||||
"/docs",
|
||||
|
|
@ -808,6 +847,9 @@ func TestAPISDKCmd_Good_TempSpecUsesMetadataFlags(t *testing.T) {
|
|||
if info["description"] != "Custom SDK description" {
|
||||
t.Fatalf("expected custom description, got %v", info["description"])
|
||||
}
|
||||
if info["summary"] != "Custom SDK overview" {
|
||||
t.Fatalf("expected custom summary, got %v", info["summary"])
|
||||
}
|
||||
if info["version"] != "9.9.9" {
|
||||
t.Fatalf("expected custom version, got %v", info["version"])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import goapi "dappco.re/go/core/api"
|
|||
|
||||
type specBuilderConfig struct {
|
||||
title string
|
||||
summary string
|
||||
description string
|
||||
version string
|
||||
swaggerPath string
|
||||
|
|
@ -30,6 +31,7 @@ type specBuilderConfig struct {
|
|||
func newSpecBuilder(cfg specBuilderConfig) (*goapi.SpecBuilder, error) {
|
||||
builder := &goapi.SpecBuilder{
|
||||
Title: cfg.title,
|
||||
Summary: cfg.summary,
|
||||
Description: cfg.description,
|
||||
Version: cfg.version,
|
||||
SwaggerPath: cfg.swaggerPath,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
// SpecBuilder constructs an OpenAPI 3.1 specification from registered RouteGroups.
|
||||
// Title, Description, Version, and optional contact/licence/terms metadata populate the
|
||||
// Title, Summary, Description, Version, and optional contact/licence/terms metadata populate the
|
||||
// OpenAPI info block. Top-level external documentation metadata is also supported.
|
||||
//
|
||||
// Example:
|
||||
|
|
@ -22,6 +22,7 @@ import (
|
|||
// spec, err := builder.Build(engine.Groups())
|
||||
type SpecBuilder struct {
|
||||
Title string
|
||||
Summary string
|
||||
Description string
|
||||
Version string
|
||||
SwaggerPath string
|
||||
|
|
@ -65,6 +66,7 @@ func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) {
|
|||
"jsonSchemaDialect": openAPIDialect,
|
||||
"info": map[string]any{
|
||||
"title": sb.Title,
|
||||
"summary": sb.Summary,
|
||||
"description": sb.Description,
|
||||
"version": sb.Version,
|
||||
},
|
||||
|
|
@ -86,6 +88,11 @@ func (sb *SpecBuilder) Build(groups []RouteGroup) ([]byte, error) {
|
|||
}
|
||||
spec["info"].(map[string]any)["license"] = license
|
||||
}
|
||||
if sb.Summary != "" {
|
||||
spec["info"].(map[string]any)["summary"] = sb.Summary
|
||||
} else {
|
||||
delete(spec["info"].(map[string]any), "summary")
|
||||
}
|
||||
|
||||
if swaggerPath := strings.TrimSpace(sb.SwaggerPath); swaggerPath != "" {
|
||||
spec["x-swagger-ui-path"] = normaliseSwaggerPath(swaggerPath)
|
||||
|
|
|
|||
|
|
@ -633,6 +633,30 @@ func TestSpecBuilder_Good_InfoIncludesLicenseMetadata(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_InfoIncludesSummary(t *testing.T) {
|
||||
sb := &api.SpecBuilder{
|
||||
Title: "Test",
|
||||
Summary: "Concise API overview",
|
||||
Description: "Summary test API",
|
||||
Version: "1.2.3",
|
||||
}
|
||||
|
||||
data, err := sb.Build(nil)
|
||||
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)
|
||||
}
|
||||
|
||||
info := spec["info"].(map[string]any)
|
||||
if info["summary"] != "Concise API overview" {
|
||||
t.Fatalf("expected summary to be preserved, got %v", info["summary"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecBuilder_Good_InfoIncludesContactMetadata(t *testing.T) {
|
||||
sb := &api.SpecBuilder{
|
||||
Title: "Test",
|
||||
|
|
|
|||
14
options.go
14
options.go
|
|
@ -195,6 +195,7 @@ func WithSunset(sunsetDate, replacement string) Option {
|
|||
|
||||
// WithSwagger enables the Swagger UI at /swagger/ by default.
|
||||
// The title, description, and version populate the OpenAPI info block.
|
||||
// Use WithSwaggerSummary() to set the optional info.summary field.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
|
|
@ -208,6 +209,19 @@ func WithSwagger(title, description, version string) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithSwaggerSummary adds the OpenAPI info.summary field to generated specs.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// api.WithSwaggerSummary("Service overview")
|
||||
func WithSwaggerSummary(summary string) Option {
|
||||
return func(e *Engine) {
|
||||
if summary != "" {
|
||||
e.swaggerSummary = summary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithSwaggerPath sets a custom URL path for the Swagger UI.
|
||||
// The default path is "/swagger".
|
||||
//
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ func (e *Engine) OpenAPISpecBuilder() *SpecBuilder {
|
|||
transport := e.TransportConfig()
|
||||
builder := &SpecBuilder{
|
||||
Title: e.swaggerTitle,
|
||||
Summary: e.swaggerSummary,
|
||||
Description: e.swaggerDesc,
|
||||
Version: e.swaggerVersion,
|
||||
TermsOfService: e.swaggerTermsOfService,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ func TestEngine_Good_OpenAPISpecBuilderCarriesEngineMetadata(t *testing.T) {
|
|||
broker := api.NewSSEBroker()
|
||||
e, err := api.New(
|
||||
api.WithSwagger("Engine API", "Engine metadata", "2.0.0"),
|
||||
api.WithSwaggerSummary("Engine overview"),
|
||||
api.WithSwaggerPath("/docs"),
|
||||
api.WithSwaggerTermsOfService("https://example.com/terms"),
|
||||
api.WithSwaggerContact("API Support", "https://example.com/support", "support@example.com"),
|
||||
|
|
@ -67,6 +68,9 @@ func TestEngine_Good_OpenAPISpecBuilderCarriesEngineMetadata(t *testing.T) {
|
|||
if info["version"] != "2.0.0" {
|
||||
t.Fatalf("expected version 2.0.0, got %v", info["version"])
|
||||
}
|
||||
if info["summary"] != "Engine overview" {
|
||||
t.Fatalf("expected summary Engine overview, got %v", info["summary"])
|
||||
}
|
||||
|
||||
if got := spec["x-swagger-ui-path"]; got != "/docs" {
|
||||
t.Fatalf("expected x-swagger-ui-path=/docs, got %v", got)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue