Skip to content

Commit

Permalink
feat: adding aepcli core openapi convert command
Browse files Browse the repository at this point in the history
exposing aep-lib's conversion functionality as a 
command-line will help with consumption of these 
aep-adjacent OpenAPI specifications in clients
not written in GoLang.
  • Loading branch information
toumorokoshi committed Nov 16, 2024
1 parent f6c1159 commit 3255956
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 109 deletions.
186 changes: 186 additions & 0 deletions cmd/aepcli/core.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package main

import (
"fmt"
"log/slog"
"os"

"github.com/aep-dev/aep-lib-go/pkg/api"
"github.com/aep-dev/aep-lib-go/pkg/openapi"
"github.com/aep-dev/aepcli/internal/config"
"github.com/spf13/cobra"
)

func handleCoreCommand(additionalArgs []string, configFile string) error {
coreCmd := &cobra.Command{
Use: "core",
Args: cobra.MinimumNArgs(1),
Short: "Core API management commands",
}

coreCmd.AddCommand(openAPICommand())
coreCmd.AddCommand(configCmd(configFile))

coreCmd.SetArgs(additionalArgs)
if err := coreCmd.Execute(); err != nil {
return fmt.Errorf("error executing core command: %v", err)
}
return nil
}

func openAPICommand() *cobra.Command {
c := &cobra.Command{
Use: "openapi",
Short: "OpenAPI commands",
}
var inputPath string
var outputPath string
var pathPrefix string

convertCmd := &cobra.Command{
Use: "convert",
Short: "Best effort conversion of OpenAPI specification to an AEP API",
Run: func(cmd *cobra.Command, args []string) {
if inputPath == "" {
fmt.Println("Input path is required")
os.Exit(1)
}
slog.Debug("Converting OpenAPI spec", "inputPath", inputPath, "outputPath", outputPath, "pathPrefix", pathPrefix)

originalOAS, err := openapi.FetchOpenAPI(inputPath)
if err != nil {
fmt.Printf("Error fetching OpenAPI spec: %v\n", err)
os.Exit(1)
}

api, err := api.GetAPI(originalOAS, "", pathPrefix)
if err != nil {
fmt.Printf("Error converting to AEP API: %v\n", err)
os.Exit(1)
}

finalOAS, err := api.ConvertToOpenAPIBytes()
if err != nil {
fmt.Printf("Error converting to OpenAPI: %v\n", err)
os.Exit(1)
}

if outputPath == "" {
fmt.Println(string(finalOAS))
} else {
err = os.WriteFile(outputPath, []byte(finalOAS), 0644)
if err != nil {
fmt.Printf("Error writing output file: %v\n", err)
os.Exit(1)
}
}

},
}

convertCmd.Flags().StringVarP(&inputPath, "input", "i", "", "Input OpenAPI specification file path")
convertCmd.Flags().StringVarP(&outputPath, "output", "o", "", "Output file path. If unset, print to stdout")
convertCmd.Flags().StringVar(&pathPrefix, "path-prefix", "", "Path prefix to strip from paths when evaluating resource hierarchy")
convertCmd.MarkFlagRequired("input")

c.AddCommand(convertCmd)
return c
}

func configCmd(configFile string) *cobra.Command {
var openAPIPath string
var overwrite bool
var api config.API
var serverURL string
var headers []string
var pathPrefix string

configCmd := &cobra.Command{
Use: "config",
Short: "Manage core API configurations",
}

addCmd := &cobra.Command{
Use: "add [name]",
Short: "Add a new core API configuration",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
api = config.API{
Name: args[0],
OpenAPIPath: openAPIPath,
ServerURL: serverURL,
Headers: headers,
PathPrefix: pathPrefix,
}
if err := config.WriteAPIWithName(configFile, api, overwrite); err != nil {
fmt.Printf("Error writing API config: %v\n", err)
os.Exit(1)
}
fmt.Printf("Core API configuration '%s' added successfully\n", args[0])
},
}

addCmd.Flags().StringVar(&openAPIPath, "openapi-path", "", "Path to OpenAPI specification file")
addCmd.Flags().StringArrayVar(&headers, "header", []string{}, "Headers in format key=value")
addCmd.Flags().StringVar(&serverURL, "server-url", "", "Server URL")
addCmd.Flags().StringVar(&pathPrefix, "path-prefix", "", "Path prefix")
addCmd.Flags().BoolVar(&overwrite, "overwrite", false, "Overwrite existing configuration")

readCmd := &cobra.Command{
Use: "get [name]",
Short: "Get an API configuration",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg, err := config.ReadConfigFromFile(configFile)
if err != nil {
fmt.Printf("Error reading config file: %v\n", err)
os.Exit(1)
}

api, exists := cfg.APIs[args[0]]
if !exists {
fmt.Printf("No API configuration found with name '%s'\n", args[0])
os.Exit(1)
}

fmt.Printf("Name: %s\n", api.Name)
fmt.Printf("OpenAPI Path: %s\n", api.OpenAPIPath)
fmt.Printf("Server URL: %s\n", api.ServerURL)
fmt.Printf("Headers: %v\n", api.Headers)
fmt.Printf("Path Prefix: %s\n", api.PathPrefix)
},
}

listCmd := &cobra.Command{
Use: "list",
Short: "List all API configurations",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
apis, err := config.ListAPIs(configFile)
if err != nil {
fmt.Printf("Error listing APIs: %v\n", err)
os.Exit(1)
}

if len(apis) == 0 {
fmt.Println("No API configurations found")
return
}

for _, api := range apis {
fmt.Printf("Name: %s\n", api.Name)
fmt.Printf("OpenAPI Path: %s\n", api.OpenAPIPath)
fmt.Printf("Server URL: %s\n", api.ServerURL)
fmt.Printf("Headers: %v\n", api.Headers)
fmt.Printf("Path Prefix: %s\n", api.PathPrefix)
fmt.Println()
}
},
}

configCmd.AddCommand(addCmd)
configCmd.AddCommand(readCmd)
configCmd.AddCommand(listCmd)

return configCmd
}
108 changes: 0 additions & 108 deletions cmd/aepcli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,111 +152,3 @@ func setLogLevel(levelAsString string) error {
slog.SetLogLoggerLevel(level)
return nil
}

func handleCoreCommand(additionalArgs []string, configFile string) error {
var openAPIPath string
var overwrite bool
var api config.API
var serverURL string
var headers []string
var pathPrefix string

coreCmd := &cobra.Command{
Use: "core",
Short: "Core API management commands",
}

configCmd := &cobra.Command{
Use: "config",
Short: "Manage core API configurations",
}

addCmd := &cobra.Command{
Use: "add [name]",
Short: "Add a new core API configuration",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
api = config.API{
Name: args[0],
OpenAPIPath: openAPIPath,
ServerURL: serverURL,
Headers: headers,
PathPrefix: pathPrefix,
}
if err := config.WriteAPIWithName(configFile, api, overwrite); err != nil {
fmt.Printf("Error writing API config: %v\n", err)
os.Exit(1)
}
fmt.Printf("Core API configuration '%s' added successfully\n", args[0])
},
}

addCmd.Flags().StringVar(&openAPIPath, "openapi-path", "", "Path to OpenAPI specification file")
addCmd.Flags().StringArrayVar(&headers, "header", []string{}, "Headers in format key=value")
addCmd.Flags().StringVar(&serverURL, "server-url", "", "Server URL")
addCmd.Flags().StringVar(&pathPrefix, "path-prefix", "", "Path prefix")
addCmd.Flags().BoolVar(&overwrite, "overwrite", false, "Overwrite existing configuration")

readCmd := &cobra.Command{
Use: "get [name]",
Short: "Get an API configuration",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg, err := config.ReadConfigFromFile(configFile)
if err != nil {
fmt.Printf("Error reading config file: %v\n", err)
os.Exit(1)
}

api, exists := cfg.APIs[args[0]]
if !exists {
fmt.Printf("No API configuration found with name '%s'\n", args[0])
os.Exit(1)
}

fmt.Printf("Name: %s\n", api.Name)
fmt.Printf("OpenAPI Path: %s\n", api.OpenAPIPath)
fmt.Printf("Server URL: %s\n", api.ServerURL)
fmt.Printf("Headers: %v\n", api.Headers)
fmt.Printf("Path Prefix: %s\n", api.PathPrefix)
},
}

listCmd := &cobra.Command{
Use: "list",
Short: "List all API configurations",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
apis, err := config.ListAPIs(configFile)
if err != nil {
fmt.Printf("Error listing APIs: %v\n", err)
os.Exit(1)
}

if len(apis) == 0 {
fmt.Println("No API configurations found")
return
}

for _, api := range apis {
fmt.Printf("Name: %s\n", api.Name)
fmt.Printf("OpenAPI Path: %s\n", api.OpenAPIPath)
fmt.Printf("Server URL: %s\n", api.ServerURL)
fmt.Printf("Headers: %v\n", api.Headers)
fmt.Printf("Path Prefix: %s\n", api.PathPrefix)
fmt.Println()
}
},
}

configCmd.AddCommand(addCmd)
configCmd.AddCommand(readCmd)
configCmd.AddCommand(listCmd)
coreCmd.AddCommand(configCmd)

coreCmd.SetArgs(additionalArgs)
if err := coreCmd.Execute(); err != nil {
return fmt.Errorf("error executing core command: %v", err)
}
return nil
}
10 changes: 10 additions & 0 deletions docs/userguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,16 @@ Request: GET http://localhost:8081/publishers/standard-house/books/foo

See `aepcli core --help` for commands for aepcli (e.g. config)

#### convert

Parse an existing OpenAPI definition as much as possible and convert it to an AEP-compliant OpenAPI definition.

This is helpful as a tool to do OpenAPI conversion when it is difficult to update the definition (e.g. the generation logic is in a third-party codebase). It's availability as a CLI also simplifies the process of integrating into CD processes to publish the spec publicly.

```bash
aepcli core openapi convert --input-path=openapis/bookstore.json --output-path=openapis/bookstore.aep.json --path-prefix=/bookstore
```

## OpenAPI Definitions

### OAS definitions supported
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.22.3

require (
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/aep-dev/aep-lib-go v0.0.0-20241109204312-789b93c37d17 // indirect
github.com/aep-dev/aep-lib-go v0.0.0-20241116210104-baaf5068c543 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/aep-dev/aep-lib-go v0.0.0-20241109204312-789b93c37d17 h1:I6JqfEQyyMZ7jMTFv2OrLBOzqCa8dFiu/FSsEx0Bty0=
github.com/aep-dev/aep-lib-go v0.0.0-20241109204312-789b93c37d17/go.mod h1:M+h1D6T2uIUPelmaEsJbjR6JhqKsTlPX3lxp25zQQsk=
github.com/aep-dev/aep-lib-go v0.0.0-20241116073126-52817379fc1d h1:juPQ462ve7LE4S7u++5ZyVY4F/VueXXLa6QIr6LpVFc=
github.com/aep-dev/aep-lib-go v0.0.0-20241116073126-52817379fc1d/go.mod h1:M+h1D6T2uIUPelmaEsJbjR6JhqKsTlPX3lxp25zQQsk=
github.com/aep-dev/aep-lib-go v0.0.0-20241116210104-baaf5068c543 h1:47/49jTwjRRZWLiiaWvXNWf+6p7IqLrkWwzPsGS2scI=
github.com/aep-dev/aep-lib-go v0.0.0-20241116210104-baaf5068c543/go.mod h1:M+h1D6T2uIUPelmaEsJbjR6JhqKsTlPX3lxp25zQQsk=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down

0 comments on commit 3255956

Please sign in to comment.