@@ -26,6 +26,7 @@ import (
2626 "path/filepath"
2727 "regexp"
2828 "slices"
29+ "strconv"
2930 "strings"
3031
3132 "log"
@@ -92,12 +93,15 @@ func (parser Parser) WriteYaml(filePath string) {
9293 doc , _ := loader .LoadFromFile (filePath )
9394 _ = doc .Validate (ctx )
9495
95- resourcePaths := findResources (doc )
96+ resources := findResources (doc )
9697 productPath := buildProduct (filePath , parser .Output , doc , header )
9798
9899 log .Printf ("Generated product %+v/product.yaml" , productPath )
99- for _ , pathArray := range resourcePaths {
100- resource := buildResource (filePath , pathArray [0 ], pathArray [1 ], doc )
100+ for name , resource := range resources {
101+ if resource .create == nil {
102+ continue
103+ }
104+ resource := buildResource (filePath , name , resource , doc )
101105
102106 // marshal method
103107 var yamlContent bytes.Buffer
@@ -130,24 +134,69 @@ func (parser Parser) WriteYaml(filePath string) {
130134 }
131135}
132136
133- func findResources (doc * openapi3.T ) [][]string {
134- var resourcePaths [][]string
137+ type resourceOp struct {
138+ path string
139+ async bool
140+ }
135141
136- pathMap := doc .Paths .Map ()
137- for key , pathValue := range pathMap {
138- if pathValue .Post == nil {
139- continue
142+ type resource struct {
143+ // nil if not defined
144+ create , update , delete * resourceOp
145+ }
146+
147+ func anyToBool (a any ) bool {
148+ switch v := a .(type ) {
149+ case bool :
150+ return v
151+ case string :
152+ if b , err := strconv .ParseBool (v ); err == nil {
153+ return b
154+ }
155+ panic (fmt .Sprintf ("cannot parse expected boolean value, found string: %q" , v ))
156+ default :
157+ panic (fmt .Sprintf ("unexpected type: %T" , v ))
158+ }
159+ }
160+
161+ func buildOperation (resourcePath string , op * openapi3.Operation , prefix string ) (string , * resourceOp ) {
162+ if op == nil {
163+ return "" , nil
164+ }
165+ if strings .HasPrefix (op .OperationID , prefix ) {
166+ resourceName := strings .Replace (op .OperationID , prefix , "" , 1 )
167+ async := false
168+ if a , ok := op .Extensions ["x-google-lro" ]; ok {
169+ async = anyToBool (a )
170+ }
171+ return resourceName , & resourceOp {path : resourcePath , async : async }
172+ }
173+ return "" , nil
174+ }
175+
176+ func findResources (doc * openapi3.T ) map [string ]* resource {
177+ resources := make (map [string ]* resource )
178+ getDefault := func (n string ) * resource {
179+ r , ok := resources [n ]
180+ if ! ok {
181+ r = & resource {}
182+ resources [n ] = r
140183 }
184+ return r
185+ }
141186
142- // Not very clever way of identifying create resource methods
143- if strings .HasPrefix (pathValue .Post .OperationID , "Create" ) {
144- resourcePath := key
145- resourceName := strings .Replace (pathValue .Post .OperationID , "Create" , "" , 1 )
146- resourcePaths = append (resourcePaths , []string {resourcePath , resourceName })
187+ for key , pathValue := range doc .Paths .Map () {
188+ if name , op := buildOperation (key , pathValue .Post , "Create" ); op != nil {
189+ getDefault (name ).create = op
190+ }
191+ if name , op := buildOperation (key , pathValue .Delete , "Delete" ); op != nil {
192+ getDefault (name ).delete = op
193+ }
194+ if name , op := buildOperation (key , pathValue .Patch , "Update" ); op != nil {
195+ getDefault (name ).update = op
147196 }
148197 }
149198
150- return resourcePaths
199+ return resources
151200}
152201
153202func buildProduct (filePath , output string , root * openapi3.T , header []byte ) string {
@@ -229,8 +278,9 @@ func stripVersion(path string) string {
229278 return re .ReplaceAllString (path , "" )
230279}
231280
232- func buildResource (filePath , resourcePath , resourceName string , root * openapi3.T ) api.Resource {
281+ func buildResource (filePath , resourceName string , in * resource , root * openapi3.T ) api.Resource {
233282 resource := api.Resource {}
283+ resourcePath := in .create .path
234284
235285 parsedObjects := parseOpenApi (resourcePath , resourceName , root )
236286
@@ -255,14 +305,25 @@ func buildResource(filePath, resourcePath, resourceName string, root *openapi3.T
255305 async := api .NewAsync ()
256306 async .Operation .BaseUrl = "{{op_id}}"
257307 async .Result .ResourceInsideResponse = true
308+ // Clear the default, we will attach the right values below
309+ async .Actions = nil
258310 resource .Async = async
311+ if in .create .async {
312+ resource .Async .Actions = append (resource .Async .Actions , "create" )
313+ }
259314
260- if hasUpdate ( resourceName , root ) {
315+ if in . update != nil {
261316 resource .UpdateVerb = "PATCH"
262317 resource .UpdateMask = true
318+ if in .update .async {
319+ resource .Async .Actions = append (resource .Async .Actions , "update" )
320+ }
263321 } else {
264322 resource .Immutable = true
265323 }
324+ if in .delete != nil && in .delete .async {
325+ resource .Async .Actions = append (resource .Async .Actions , "delete" )
326+ }
266327
267328 example := r.Examples {}
268329 example .Name = "name_of_example_file"
@@ -279,20 +340,6 @@ func buildResource(filePath, resourcePath, resourceName string, root *openapi3.T
279340 return resource
280341}
281342
282- func hasUpdate (resourceName string , root * openapi3.T ) bool {
283- // Create and Update have different paths in the OpenAPI spec, so look
284- // through all paths to find one that matches the expected operation name
285- for _ , pathValue := range root .Paths .Map () {
286- if pathValue .Patch == nil {
287- continue
288- }
289- if pathValue .Patch .OperationID == fmt .Sprintf ("Update%s" , resourceName ) {
290- return true
291- }
292- }
293- return false
294- }
295-
296343func parseOpenApi (resourcePath , resourceName string , root * openapi3.T ) []any {
297344 returnArray := []any {}
298345 path := root .Paths .Find (resourcePath )
0 commit comments