Skip to content

Commit 3255956

Browse files
committed
feat: adding aepcli core openapi convert command
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.
1 parent f6c1159 commit 3255956

5 files changed

Lines changed: 201 additions & 109 deletions

File tree

cmd/aepcli/core.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log/slog"
6+
"os"
7+
8+
"github.com/aep-dev/aep-lib-go/pkg/api"
9+
"github.com/aep-dev/aep-lib-go/pkg/openapi"
10+
"github.com/aep-dev/aepcli/internal/config"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func handleCoreCommand(additionalArgs []string, configFile string) error {
15+
coreCmd := &cobra.Command{
16+
Use: "core",
17+
Args: cobra.MinimumNArgs(1),
18+
Short: "Core API management commands",
19+
}
20+
21+
coreCmd.AddCommand(openAPICommand())
22+
coreCmd.AddCommand(configCmd(configFile))
23+
24+
coreCmd.SetArgs(additionalArgs)
25+
if err := coreCmd.Execute(); err != nil {
26+
return fmt.Errorf("error executing core command: %v", err)
27+
}
28+
return nil
29+
}
30+
31+
func openAPICommand() *cobra.Command {
32+
c := &cobra.Command{
33+
Use: "openapi",
34+
Short: "OpenAPI commands",
35+
}
36+
var inputPath string
37+
var outputPath string
38+
var pathPrefix string
39+
40+
convertCmd := &cobra.Command{
41+
Use: "convert",
42+
Short: "Best effort conversion of OpenAPI specification to an AEP API",
43+
Run: func(cmd *cobra.Command, args []string) {
44+
if inputPath == "" {
45+
fmt.Println("Input path is required")
46+
os.Exit(1)
47+
}
48+
slog.Debug("Converting OpenAPI spec", "inputPath", inputPath, "outputPath", outputPath, "pathPrefix", pathPrefix)
49+
50+
originalOAS, err := openapi.FetchOpenAPI(inputPath)
51+
if err != nil {
52+
fmt.Printf("Error fetching OpenAPI spec: %v\n", err)
53+
os.Exit(1)
54+
}
55+
56+
api, err := api.GetAPI(originalOAS, "", pathPrefix)
57+
if err != nil {
58+
fmt.Printf("Error converting to AEP API: %v\n", err)
59+
os.Exit(1)
60+
}
61+
62+
finalOAS, err := api.ConvertToOpenAPIBytes()
63+
if err != nil {
64+
fmt.Printf("Error converting to OpenAPI: %v\n", err)
65+
os.Exit(1)
66+
}
67+
68+
if outputPath == "" {
69+
fmt.Println(string(finalOAS))
70+
} else {
71+
err = os.WriteFile(outputPath, []byte(finalOAS), 0644)
72+
if err != nil {
73+
fmt.Printf("Error writing output file: %v\n", err)
74+
os.Exit(1)
75+
}
76+
}
77+
78+
},
79+
}
80+
81+
convertCmd.Flags().StringVarP(&inputPath, "input", "i", "", "Input OpenAPI specification file path")
82+
convertCmd.Flags().StringVarP(&outputPath, "output", "o", "", "Output file path. If unset, print to stdout")
83+
convertCmd.Flags().StringVar(&pathPrefix, "path-prefix", "", "Path prefix to strip from paths when evaluating resource hierarchy")
84+
convertCmd.MarkFlagRequired("input")
85+
86+
c.AddCommand(convertCmd)
87+
return c
88+
}
89+
90+
func configCmd(configFile string) *cobra.Command {
91+
var openAPIPath string
92+
var overwrite bool
93+
var api config.API
94+
var serverURL string
95+
var headers []string
96+
var pathPrefix string
97+
98+
configCmd := &cobra.Command{
99+
Use: "config",
100+
Short: "Manage core API configurations",
101+
}
102+
103+
addCmd := &cobra.Command{
104+
Use: "add [name]",
105+
Short: "Add a new core API configuration",
106+
Args: cobra.ExactArgs(1),
107+
Run: func(cmd *cobra.Command, args []string) {
108+
api = config.API{
109+
Name: args[0],
110+
OpenAPIPath: openAPIPath,
111+
ServerURL: serverURL,
112+
Headers: headers,
113+
PathPrefix: pathPrefix,
114+
}
115+
if err := config.WriteAPIWithName(configFile, api, overwrite); err != nil {
116+
fmt.Printf("Error writing API config: %v\n", err)
117+
os.Exit(1)
118+
}
119+
fmt.Printf("Core API configuration '%s' added successfully\n", args[0])
120+
},
121+
}
122+
123+
addCmd.Flags().StringVar(&openAPIPath, "openapi-path", "", "Path to OpenAPI specification file")
124+
addCmd.Flags().StringArrayVar(&headers, "header", []string{}, "Headers in format key=value")
125+
addCmd.Flags().StringVar(&serverURL, "server-url", "", "Server URL")
126+
addCmd.Flags().StringVar(&pathPrefix, "path-prefix", "", "Path prefix")
127+
addCmd.Flags().BoolVar(&overwrite, "overwrite", false, "Overwrite existing configuration")
128+
129+
readCmd := &cobra.Command{
130+
Use: "get [name]",
131+
Short: "Get an API configuration",
132+
Args: cobra.ExactArgs(1),
133+
Run: func(cmd *cobra.Command, args []string) {
134+
cfg, err := config.ReadConfigFromFile(configFile)
135+
if err != nil {
136+
fmt.Printf("Error reading config file: %v\n", err)
137+
os.Exit(1)
138+
}
139+
140+
api, exists := cfg.APIs[args[0]]
141+
if !exists {
142+
fmt.Printf("No API configuration found with name '%s'\n", args[0])
143+
os.Exit(1)
144+
}
145+
146+
fmt.Printf("Name: %s\n", api.Name)
147+
fmt.Printf("OpenAPI Path: %s\n", api.OpenAPIPath)
148+
fmt.Printf("Server URL: %s\n", api.ServerURL)
149+
fmt.Printf("Headers: %v\n", api.Headers)
150+
fmt.Printf("Path Prefix: %s\n", api.PathPrefix)
151+
},
152+
}
153+
154+
listCmd := &cobra.Command{
155+
Use: "list",
156+
Short: "List all API configurations",
157+
Args: cobra.NoArgs,
158+
Run: func(cmd *cobra.Command, args []string) {
159+
apis, err := config.ListAPIs(configFile)
160+
if err != nil {
161+
fmt.Printf("Error listing APIs: %v\n", err)
162+
os.Exit(1)
163+
}
164+
165+
if len(apis) == 0 {
166+
fmt.Println("No API configurations found")
167+
return
168+
}
169+
170+
for _, api := range apis {
171+
fmt.Printf("Name: %s\n", api.Name)
172+
fmt.Printf("OpenAPI Path: %s\n", api.OpenAPIPath)
173+
fmt.Printf("Server URL: %s\n", api.ServerURL)
174+
fmt.Printf("Headers: %v\n", api.Headers)
175+
fmt.Printf("Path Prefix: %s\n", api.PathPrefix)
176+
fmt.Println()
177+
}
178+
},
179+
}
180+
181+
configCmd.AddCommand(addCmd)
182+
configCmd.AddCommand(readCmd)
183+
configCmd.AddCommand(listCmd)
184+
185+
return configCmd
186+
}

cmd/aepcli/main.go

Lines changed: 0 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -152,111 +152,3 @@ func setLogLevel(levelAsString string) error {
152152
slog.SetLogLoggerLevel(level)
153153
return nil
154154
}
155-
156-
func handleCoreCommand(additionalArgs []string, configFile string) error {
157-
var openAPIPath string
158-
var overwrite bool
159-
var api config.API
160-
var serverURL string
161-
var headers []string
162-
var pathPrefix string
163-
164-
coreCmd := &cobra.Command{
165-
Use: "core",
166-
Short: "Core API management commands",
167-
}
168-
169-
configCmd := &cobra.Command{
170-
Use: "config",
171-
Short: "Manage core API configurations",
172-
}
173-
174-
addCmd := &cobra.Command{
175-
Use: "add [name]",
176-
Short: "Add a new core API configuration",
177-
Args: cobra.ExactArgs(1),
178-
Run: func(cmd *cobra.Command, args []string) {
179-
api = config.API{
180-
Name: args[0],
181-
OpenAPIPath: openAPIPath,
182-
ServerURL: serverURL,
183-
Headers: headers,
184-
PathPrefix: pathPrefix,
185-
}
186-
if err := config.WriteAPIWithName(configFile, api, overwrite); err != nil {
187-
fmt.Printf("Error writing API config: %v\n", err)
188-
os.Exit(1)
189-
}
190-
fmt.Printf("Core API configuration '%s' added successfully\n", args[0])
191-
},
192-
}
193-
194-
addCmd.Flags().StringVar(&openAPIPath, "openapi-path", "", "Path to OpenAPI specification file")
195-
addCmd.Flags().StringArrayVar(&headers, "header", []string{}, "Headers in format key=value")
196-
addCmd.Flags().StringVar(&serverURL, "server-url", "", "Server URL")
197-
addCmd.Flags().StringVar(&pathPrefix, "path-prefix", "", "Path prefix")
198-
addCmd.Flags().BoolVar(&overwrite, "overwrite", false, "Overwrite existing configuration")
199-
200-
readCmd := &cobra.Command{
201-
Use: "get [name]",
202-
Short: "Get an API configuration",
203-
Args: cobra.ExactArgs(1),
204-
Run: func(cmd *cobra.Command, args []string) {
205-
cfg, err := config.ReadConfigFromFile(configFile)
206-
if err != nil {
207-
fmt.Printf("Error reading config file: %v\n", err)
208-
os.Exit(1)
209-
}
210-
211-
api, exists := cfg.APIs[args[0]]
212-
if !exists {
213-
fmt.Printf("No API configuration found with name '%s'\n", args[0])
214-
os.Exit(1)
215-
}
216-
217-
fmt.Printf("Name: %s\n", api.Name)
218-
fmt.Printf("OpenAPI Path: %s\n", api.OpenAPIPath)
219-
fmt.Printf("Server URL: %s\n", api.ServerURL)
220-
fmt.Printf("Headers: %v\n", api.Headers)
221-
fmt.Printf("Path Prefix: %s\n", api.PathPrefix)
222-
},
223-
}
224-
225-
listCmd := &cobra.Command{
226-
Use: "list",
227-
Short: "List all API configurations",
228-
Args: cobra.NoArgs,
229-
Run: func(cmd *cobra.Command, args []string) {
230-
apis, err := config.ListAPIs(configFile)
231-
if err != nil {
232-
fmt.Printf("Error listing APIs: %v\n", err)
233-
os.Exit(1)
234-
}
235-
236-
if len(apis) == 0 {
237-
fmt.Println("No API configurations found")
238-
return
239-
}
240-
241-
for _, api := range apis {
242-
fmt.Printf("Name: %s\n", api.Name)
243-
fmt.Printf("OpenAPI Path: %s\n", api.OpenAPIPath)
244-
fmt.Printf("Server URL: %s\n", api.ServerURL)
245-
fmt.Printf("Headers: %v\n", api.Headers)
246-
fmt.Printf("Path Prefix: %s\n", api.PathPrefix)
247-
fmt.Println()
248-
}
249-
},
250-
}
251-
252-
configCmd.AddCommand(addCmd)
253-
configCmd.AddCommand(readCmd)
254-
configCmd.AddCommand(listCmd)
255-
coreCmd.AddCommand(configCmd)
256-
257-
coreCmd.SetArgs(additionalArgs)
258-
if err := coreCmd.Execute(); err != nil {
259-
return fmt.Errorf("error executing core command: %v", err)
260-
}
261-
return nil
262-
}

docs/userguide.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,16 @@ Request: GET http://localhost:8081/publishers/standard-house/books/foo
195195

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

198+
#### convert
199+
200+
Parse an existing OpenAPI definition as much as possible and convert it to an AEP-compliant OpenAPI definition.
201+
202+
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.
203+
204+
```bash
205+
aepcli core openapi convert --input-path=openapis/bookstore.json --output-path=openapis/bookstore.aep.json --path-prefix=/bookstore
206+
```
207+
198208
## OpenAPI Definitions
199209

200210
### OAS definitions supported

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.22.3
44

55
require (
66
github.com/BurntSushi/toml v1.4.0 // indirect
7-
github.com/aep-dev/aep-lib-go v0.0.0-20241109204312-789b93c37d17 // indirect
7+
github.com/aep-dev/aep-lib-go v0.0.0-20241116210104-baaf5068c543 // indirect
88
github.com/davecgh/go-spew v1.1.1 // indirect
99
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1010
github.com/pmezard/go-difflib v1.0.0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0
22
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
33
github.com/aep-dev/aep-lib-go v0.0.0-20241109204312-789b93c37d17 h1:I6JqfEQyyMZ7jMTFv2OrLBOzqCa8dFiu/FSsEx0Bty0=
44
github.com/aep-dev/aep-lib-go v0.0.0-20241109204312-789b93c37d17/go.mod h1:M+h1D6T2uIUPelmaEsJbjR6JhqKsTlPX3lxp25zQQsk=
5+
github.com/aep-dev/aep-lib-go v0.0.0-20241116073126-52817379fc1d h1:juPQ462ve7LE4S7u++5ZyVY4F/VueXXLa6QIr6LpVFc=
6+
github.com/aep-dev/aep-lib-go v0.0.0-20241116073126-52817379fc1d/go.mod h1:M+h1D6T2uIUPelmaEsJbjR6JhqKsTlPX3lxp25zQQsk=
7+
github.com/aep-dev/aep-lib-go v0.0.0-20241116210104-baaf5068c543 h1:47/49jTwjRRZWLiiaWvXNWf+6p7IqLrkWwzPsGS2scI=
8+
github.com/aep-dev/aep-lib-go v0.0.0-20241116210104-baaf5068c543/go.mod h1:M+h1D6T2uIUPelmaEsJbjR6JhqKsTlPX3lxp25zQQsk=
59
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
610
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
711
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

0 commit comments

Comments
 (0)