feat: add SDK codegen wrapper for openapi-generator-cli
Co-Authored-By: Virgil <virgil@lethean.io> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
303779f527
commit
a09a4e9db9
2 changed files with 190 additions and 0 deletions
96
codegen.go
Normal file
96
codegen.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// 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.
|
||||
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().
|
||||
func (g *SDKGenerator) Generate(ctx context.Context, language string) error {
|
||||
generator, ok := supportedLanguages[language]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported language %q: supported languages are %v", language, SupportedLanguages())
|
||||
}
|
||||
|
||||
if _, err := os.Stat(g.SpecPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("spec file not found: %s", g.SpecPath)
|
||||
}
|
||||
|
||||
outputDir := filepath.Join(g.OutputDir, language)
|
||||
if err := os.MkdirAll(outputDir, 0o755); err != nil {
|
||||
return fmt.Errorf("create output directory: %w", err)
|
||||
}
|
||||
|
||||
args := g.buildArgs(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 fmt.Errorf("openapi-generator-cli failed for %s: %w", language, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildArgs constructs the openapi-generator-cli command arguments.
|
||||
func (g *SDKGenerator) buildArgs(generator, outputDir string) []string {
|
||||
args := []string{
|
||||
"generate",
|
||||
"-i", g.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.
|
||||
func (g *SDKGenerator) Available() bool {
|
||||
_, err := exec.LookPath("openapi-generator-cli")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// SupportedLanguages returns the list of supported SDK target languages.
|
||||
func SupportedLanguages() []string {
|
||||
langs := make([]string, 0, len(supportedLanguages))
|
||||
for k := range supportedLanguages {
|
||||
langs = append(langs, k)
|
||||
}
|
||||
return langs
|
||||
}
|
||||
94
codegen_test.go
Normal file
94
codegen_test.go
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
api "forge.lthn.ai/core/go-api"
|
||||
)
|
||||
|
||||
// ── SDKGenerator tests ─────────────────────────────────────────────────────
|
||||
|
||||
func TestSDKGenerator_Good_SupportedLanguages(t *testing.T) {
|
||||
langs := api.SupportedLanguages()
|
||||
if len(langs) == 0 {
|
||||
t.Fatal("expected at least one supported language")
|
||||
}
|
||||
|
||||
expected := []string{"go", "typescript-fetch", "python", "java", "csharp"}
|
||||
for _, lang := range expected {
|
||||
if !slices.Contains(langs, lang) {
|
||||
t.Errorf("expected %q in supported languages, got %v", lang, langs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSDKGenerator_Bad_UnsupportedLanguage(t *testing.T) {
|
||||
gen := &api.SDKGenerator{
|
||||
SpecPath: "spec.json",
|
||||
OutputDir: t.TempDir(),
|
||||
}
|
||||
|
||||
err := gen.Generate(context.Background(), "brainfuck")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unsupported language, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unsupported language") {
|
||||
t.Fatalf("expected error to contain 'unsupported language', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSDKGenerator_Bad_MissingSpec(t *testing.T) {
|
||||
gen := &api.SDKGenerator{
|
||||
SpecPath: filepath.Join(t.TempDir(), "nonexistent.json"),
|
||||
OutputDir: t.TempDir(),
|
||||
}
|
||||
|
||||
err := gen.Generate(context.Background(), "go")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing spec file, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "spec file not found") {
|
||||
t.Fatalf("expected error to contain 'spec file not found', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSDKGenerator_Good_OutputDirCreated(t *testing.T) {
|
||||
// Write a minimal spec file so we pass the file-exists check.
|
||||
specDir := t.TempDir()
|
||||
specPath := filepath.Join(specDir, "spec.json")
|
||||
if err := os.WriteFile(specPath, []byte(`{"openapi":"3.1.0"}`), 0o644); err != nil {
|
||||
t.Fatalf("failed to write spec file: %v", err)
|
||||
}
|
||||
|
||||
outputDir := filepath.Join(t.TempDir(), "nested", "sdk")
|
||||
gen := &api.SDKGenerator{
|
||||
SpecPath: specPath,
|
||||
OutputDir: outputDir,
|
||||
}
|
||||
|
||||
// Generate will fail at the exec step (openapi-generator-cli likely not installed),
|
||||
// but the output directory should have been created before that.
|
||||
_ = gen.Generate(context.Background(), "go")
|
||||
|
||||
expected := filepath.Join(outputDir, "go")
|
||||
info, err := os.Stat(expected)
|
||||
if err != nil {
|
||||
t.Fatalf("expected output directory %s to exist, got error: %v", expected, err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
t.Fatalf("expected %s to be a directory", expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSDKGenerator_Good_Available(t *testing.T) {
|
||||
gen := &api.SDKGenerator{}
|
||||
// Just verify it returns a bool and does not panic.
|
||||
_ = gen.Available()
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue