Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ bin/api-generator:
.PHONY: gen-api-commands
gen-api-commands: bin/api-generator ## Generate api commands
@echo "==> Generating api commands"
bin/api-generator --spec ./tools/api-generator/spec.yaml --overlay ./tools/api-generator/overlays > ./internal/api/commands.go
bin/api-generator --spec ./tools/api-generator/spec.yaml --overlay ./tools/api-generator/overlays --output-type commands > ./internal/api/commands.go

.PHONY: otel
otel: ## Generate code
Expand Down
54 changes: 43 additions & 11 deletions tools/api-generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,55 @@ import (
"bytes"
"context"
_ "embed"
"errors"
"fmt"
"go/format"
"io"
"os"
"path/filepath"
"slices"
"strings"
"text/template"
"time"

"github.com/getkin/kin-openapi/openapi3"
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/api"
"github.com/speakeasy-api/openapi-overlay/pkg/overlay"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)

//go:embed commands.go.tmpl
var templateContent string
var commandsTemplateContent string

type OutputType string

const (
Commands OutputType = "commands"
Metadata OutputType = "metadata"
)

// Returns all possible values of OutputType.
func AllOutputTypes() []OutputType {
return []OutputType{Commands, Metadata}
}

func main() {
var (
specPath string
overlayPath string
specPath string
overlayPath string
outputTypeStr string
)

var rootCmd = &cobra.Command{
Use: "api-generator",
Short: "CLI which generates api command definitions from a OpenAPI spec",
RunE: func(command *cobra.Command, _ []string) error {
return run(command.Context(), specPath, overlayPath, command.OutOrStdout())
outputType := OutputType(outputTypeStr)
if !slices.Contains(AllOutputTypes(), outputType) {
return fmt.Errorf("'%s' is not a valid output type", outputType)
}

return run(command.Context(), specPath, overlayPath, outputType, command.OutOrStdout())
},
}

Expand All @@ -56,13 +75,15 @@ func main() {
_ = rootCmd.MarkFlagRequired("spec")
_ = rootCmd.MarkFlagFilename("spec")

rootCmd.Flags().StringVar(&outputTypeStr, "output-type", "", "Set output type [commands/metadata]")

if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

func run(ctx context.Context, specPath, overlayPath string, w io.Writer) error {
func run(ctx context.Context, specPath, overlayPath string, outputType OutputType, w io.Writer) error {
specFile, err := os.OpenFile(specPath, os.O_RDONLY, os.ModePerm)
if err != nil {
return err
Expand Down Expand Up @@ -94,7 +115,14 @@ func run(ctx context.Context, specPath, overlayPath string, w io.Writer) error {
overlayFiles = append(overlayFiles, overlayFile)
}

return convertSpecToAPICommands(ctx, specFile, overlayFiles, w)
switch outputType {
case Commands:
return convertSpecToAPICommands(ctx, specFile, overlayFiles, w)
case Metadata:
return errors.New("TODO")
default:
return fmt.Errorf("'%s' is not a valid outputType", outputType)
}
}

func applyOverlays(r io.Reader, overlayFiles []io.Reader) (io.Reader, error) {
Expand Down Expand Up @@ -134,6 +162,10 @@ func applyOverlays(r io.Reader, overlayFiles []io.Reader) (io.Reader, error) {
}

func convertSpecToAPICommands(ctx context.Context, r io.Reader, overlayFiles []io.Reader, w io.Writer) error {
return convertSpec(ctx, r, overlayFiles, w, specToCommands, commandsTemplateContent)
}

func convertSpec[T any](ctx context.Context, r io.Reader, overlayFiles []io.Reader, w io.Writer, mapper func(spec *openapi3.T) (T, error), templateContent string) error {
overlaySpec, err := applyOverlays(r, overlayFiles)
if err != nil {
return fmt.Errorf("failed to apply overlays, error: %w", err)
Expand All @@ -148,21 +180,21 @@ func convertSpecToAPICommands(ctx context.Context, r io.Reader, overlayFiles []i
return fmt.Errorf("spec validation failed, error: %w", err)
}

commands, err := specToCommands(spec)
commands, err := mapper(spec)
if err != nil {
return fmt.Errorf("failed convert spec to api commands: %w", err)
}

return writeCommands(w, commands)
return writeCommands(w, templateContent, commands)
}

func loadSpec(r io.Reader) (*openapi3.T, error) {
loader := openapi3.NewLoader()
return loader.LoadFromIoReader(r)
}

func writeCommands(w io.Writer, data api.GroupedAndSortedCommands) error {
tmpl, err := template.New("commands.go.tmpl").Funcs(template.FuncMap{
func writeCommands[T any](w io.Writer, templateContent string, data T) error {
tmpl, err := template.New("output").Funcs(template.FuncMap{
"currentYear": func() int {
return time.Now().UTC().Year()
},
Expand Down
18 changes: 13 additions & 5 deletions tools/api-generator/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package main
import (
"bytes"
"context"
"fmt"
"io"
"os"
"path/filepath"
Expand Down Expand Up @@ -47,13 +48,20 @@ func testSpec(t *testing.T, name, specPath string) {
overlays = []io.Reader{overlayFile}
}

buf := &bytes.Buffer{}
if err := convertSpecToAPICommands(context.Background(), specFile, overlays, buf); err != nil {
t.Fatalf("failed to convert spec into commmands, error: %s", err)
outputFunctions := map[OutputType]func(ctx context.Context, r io.Reader, overlayFiles []io.Reader, w io.Writer) error{
Commands: convertSpecToAPICommands,
}

if err := snapshotter.SnapshotWithName(name, buf.String()); err != nil {
t.Fatalf("unexpected result %s", err)
for outputType, outputTypeFunc := range outputFunctions {
buf := &bytes.Buffer{}

if err := outputTypeFunc(context.Background(), specFile, overlays, buf); err != nil {
t.Fatalf("failed to convert spec into commmands, error: %s", err)
}

if err := snapshotter.SnapshotWithName(fmt.Sprintf("%s-%s", name, outputType), buf.String()); err != nil {
t.Fatalf("unexpected result %s", err)
}
}
}

Expand Down
Loading