api/codegen.go
Snider e54dd2e370 fix(pr#2): address CodeRabbit major/critical review findings
Go:
- codegen: pass trimmed specPath to buildArgs instead of raw g.SpecPath
- cmd/sdk: use local resolvedSpecFile to avoid mutating flag variable per-invocation
- export: write to temp file + atomic rename to prevent destination truncation on failure
- openapi: gate effectiveGraphQLPath/SwaggerPath/WSPath/SSEPath on enable flags; use effectiveSwaggerPath in effectiveAuthentikPublicPaths
- cache: reject oversized replacement before mutating LRU state for existing keys
- ratelimit: move setRateLimitHeaders before c.Next() so headers are sent; hash credential headers with SHA-256 to avoid storing raw secrets; prefer validated principal from context
- response_meta: track size separately from body buffer so Size() is accurate after body rewrites and in passthrough mode
- bridge: limit request body reads with http.MaxBytesReader (10 MiB); allow missing data key in ValidateResponse for nil/zero success responses; update recorder status in writeErrorResponse
- pkg/provider/proxy: validate target scheme and host after url.Parse to catch hostless inputs
- cmd_test: snapshot/restore global spec registry in TestAPISpecCmd_Good_RegisteredSpecGroups

PHP:
- HasApiResponses.php, config.php: add declare(strict_types=1)
- RateLimitExceededException: validate Origin against cors.allowed_origins before reflecting in CORS header
- ApiUsageService: import and use Core\Api\Models\ApiKey instead of fully-qualified Mod\ path
- SeoReportService: add SSRF protection (scheme check, private-IP rejection); add .throw() for HTTP error handling; disable automatic redirects

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-07 08:38:41 +01:00

153 lines
4.1 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
package api
import (
"context"
"fmt"
"iter"
"maps"
"os"
"os/exec"
"path/filepath"
"slices"
"strings"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
)
// Supported SDK target languages.
var supportedLanguages = map[string]string{
"go": "go",
"typescript-fetch": "typescript-fetch",
"typescript-axios": "typescript-axios",
"python": "python",
"java": "java",
"csharp": "csharp-netcore",
"ruby": "ruby",
"swift": "swift5",
"kotlin": "kotlin",
"rust": "rust",
"php": "php",
}
// SDKGenerator wraps openapi-generator-cli for SDK generation.
//
// Example:
//
// gen := &api.SDKGenerator{SpecPath: "./openapi.yaml", OutputDir: "./sdk", PackageName: "service"}
type SDKGenerator struct {
// SpecPath is the path to the OpenAPI spec file (JSON or YAML).
SpecPath string
// OutputDir is the base directory for generated SDK output.
OutputDir string
// PackageName is the name used for the generated package/module.
PackageName string
}
// Generate creates an SDK for the given language using openapi-generator-cli.
// The language must be one of the supported languages returned by SupportedLanguages().
//
// Example:
//
// err := gen.Generate(context.Background(), "go")
func (g *SDKGenerator) Generate(ctx context.Context, language string) error {
if g == nil {
return coreerr.E("SDKGenerator.Generate", "generator is nil", nil)
}
if ctx == nil {
return coreerr.E("SDKGenerator.Generate", "context is nil", nil)
}
language = strings.TrimSpace(language)
generator, ok := supportedLanguages[language]
if !ok {
return coreerr.E("SDKGenerator.Generate", fmt.Sprintf("unsupported language %q: supported languages are %v", language, SupportedLanguages()), nil)
}
specPath := strings.TrimSpace(g.SpecPath)
if specPath == "" {
return coreerr.E("SDKGenerator.Generate", "spec path is required", nil)
}
if _, err := os.Stat(specPath); err != nil {
if os.IsNotExist(err) {
return coreerr.E("SDKGenerator.Generate", "spec file not found: "+specPath, nil)
}
return coreerr.E("SDKGenerator.Generate", "stat spec file", err)
}
outputBase := strings.TrimSpace(g.OutputDir)
if outputBase == "" {
return coreerr.E("SDKGenerator.Generate", "output directory is required", nil)
}
if !g.Available() {
return coreerr.E("SDKGenerator.Generate", "openapi-generator-cli not installed", nil)
}
outputDir := filepath.Join(outputBase, language)
if err := coreio.Local.EnsureDir(outputDir); err != nil {
return coreerr.E("SDKGenerator.Generate", "create output directory", err)
}
args := g.buildArgs(specPath, generator, outputDir)
cmd := exec.CommandContext(ctx, "openapi-generator-cli", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return coreerr.E("SDKGenerator.Generate", "openapi-generator-cli failed for "+language, err)
}
return nil
}
// buildArgs constructs the openapi-generator-cli command arguments.
func (g *SDKGenerator) buildArgs(specPath, generator, outputDir string) []string {
args := []string{
"generate",
"-i", specPath,
"-g", generator,
"-o", outputDir,
}
if g.PackageName != "" {
args = append(args, "--additional-properties", "packageName="+g.PackageName)
}
return args
}
// Available checks if openapi-generator-cli is installed and accessible.
//
// Example:
//
// if !gen.Available() {
// t.Fatal("openapi-generator-cli is required")
// }
func (g *SDKGenerator) Available() bool {
_, err := exec.LookPath("openapi-generator-cli")
return err == nil
}
// SupportedLanguages returns the list of supported SDK target languages
// in sorted order for deterministic output.
//
// Example:
//
// langs := api.SupportedLanguages()
func SupportedLanguages() []string {
return slices.Sorted(maps.Keys(supportedLanguages))
}
// SupportedLanguagesIter returns an iterator over supported SDK target languages in sorted order.
//
// Example:
//
// for lang := range api.SupportedLanguagesIter() {
// fmt.Println(lang)
// }
func SupportedLanguagesIter() iter.Seq[string] {
return slices.Values(SupportedLanguages())
}