Skip to content

Commit 4356b26

Browse files
slevenickJianweiQ
authored andcommitted
Singleton support for autogen parser (GoogleCloudPlatform#16057)
1 parent b3a17a0 commit 4356b26

File tree

3 files changed

+168
-40
lines changed

3 files changed

+168
-40
lines changed

mmv1/openapi_generate/parser.go

Lines changed: 103 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -99,39 +99,49 @@ func (parser Parser) WriteYaml(filePath string) {
9999
log.Printf("Generated product %+v/product.yaml", productPath)
100100
for name, resource := range resources {
101101
if resource.create == nil {
102-
continue
103-
}
104-
resource := buildResource(filePath, name, resource, doc)
102+
if resource.update == nil {
103+
continue
104+
}
105+
singleton := buildSingleton(name, resource, doc)
105106

106-
// marshal method
107-
var yamlContent bytes.Buffer
108-
resourceOutPathMarshal := filepath.Join(productPath, fmt.Sprintf("%s.yaml", resource.Name))
109-
encoder := yaml.NewEncoder(&yamlContent)
110-
encoder.SetIndent(2)
107+
parser.writeResource(singleton, productPath)
108+
} else {
109+
resource := buildResource(name, resource, doc)
111110

112-
err := encoder.Encode(&resource)
113-
if err != nil {
114-
log.Fatalf("Failed to encode: %v", err)
111+
parser.writeResource(resource, productPath)
115112
}
113+
}
114+
}
116115

117-
f, err := os.Create(resourceOutPathMarshal)
118-
if err != nil {
119-
log.Fatalf("error creating resource file %v", err)
120-
}
121-
_, err = f.Write(header)
122-
if err != nil {
123-
log.Fatalf("error writing resource file header %v", err)
124-
}
125-
_, err = f.Write(yamlContent.Bytes())
126-
if err != nil {
127-
log.Fatalf("error writing resource file %v", err)
128-
}
129-
err = f.Close()
130-
if err != nil {
131-
log.Fatalf("error closing resource file %v", err)
132-
}
133-
log.Printf("Generated resource %s", resourceOutPathMarshal)
116+
func (parser Parser) writeResource(resource api.Resource, productPath string) {
117+
// marshal method
118+
var yamlContent bytes.Buffer
119+
resourceOutPathMarshal := filepath.Join(productPath, fmt.Sprintf("%s.yaml", resource.Name))
120+
encoder := yaml.NewEncoder(&yamlContent)
121+
encoder.SetIndent(2)
122+
123+
err := encoder.Encode(&resource)
124+
if err != nil {
125+
log.Fatalf("Failed to encode: %v", err)
126+
}
127+
128+
f, err := os.Create(resourceOutPathMarshal)
129+
if err != nil {
130+
log.Fatalf("error creating resource file %v", err)
131+
}
132+
_, err = f.Write(header)
133+
if err != nil {
134+
log.Fatalf("error writing resource file header %v", err)
135+
}
136+
_, err = f.Write(yamlContent.Bytes())
137+
if err != nil {
138+
log.Fatalf("error writing resource file %v", err)
139+
}
140+
err = f.Close()
141+
if err != nil {
142+
log.Fatalf("error closing resource file %v", err)
134143
}
144+
log.Printf("Generated resource %s", resourceOutPathMarshal)
135145
}
136146

137147
type resourceOp struct {
@@ -278,11 +288,54 @@ func stripVersion(path string) string {
278288
return re.ReplaceAllString(path, "")
279289
}
280290

281-
func buildResource(filePath, resourceName string, in *resource, root *openapi3.T) api.Resource {
291+
func buildSingleton(resourceName string, in *resource, root *openapi3.T) api.Resource {
292+
resource := api.Resource{}
293+
resourcePath := in.update.path
294+
295+
op := root.Paths.Find(resourcePath).Patch
296+
parsedObjects := parseOpenApi(resourcePath, resourceName, op)
297+
298+
parameters := parsedObjects[0].([]*api.Type)
299+
properties := parsedObjects[1].([]*api.Type)
300+
301+
baseUrl := baseUrl(resourcePath)
302+
selfLink := baseUrl
303+
304+
resource.Name = resourceName
305+
resource.BaseUrl = baseUrl
306+
resource.Parameters = parameters
307+
resource.Properties = properties
308+
resource.SelfLink = selfLink
309+
resource.CreateUrl = fmt.Sprintf("%s=?updateMask=*", baseUrl)
310+
311+
resource.CreateVerb = "PATCH"
312+
313+
resource.UpdateVerb = "PATCH"
314+
resource.UpdateMask = true
315+
if in.update.async {
316+
resource.AutogenAsync = true
317+
async := api.NewAsync()
318+
async.Operation.BaseUrl = "{{op_id}}"
319+
async.Result.ResourceInsideResponse = true
320+
// Clear the default, we will attach the right values below
321+
async.Actions = nil
322+
resource.Async = async
323+
resource.Async.Actions = append(resource.Async.Actions, "update")
324+
}
325+
326+
resource.ExcludeDelete = true
327+
328+
resource = attachStandardFunctionality(resource)
329+
330+
return resource
331+
}
332+
333+
func buildResource(resourceName string, in *resource, root *openapi3.T) api.Resource {
282334
resource := api.Resource{}
283335
resourcePath := in.create.path
284336

285-
parsedObjects := parseOpenApi(resourcePath, resourceName, root)
337+
op := root.Paths.Find(resourcePath).Post
338+
parsedObjects := parseOpenApi(resourcePath, resourceName, op)
286339

287340
parameters := parsedObjects[0].([]*api.Type)
288341
properties := parsedObjects[1].([]*api.Type)
@@ -296,10 +349,7 @@ func buildResource(filePath, resourceName string, in *resource, root *openapi3.T
296349
resource.Parameters = parameters
297350
resource.Properties = properties
298351
resource.SelfLink = selfLink
299-
resource.IdFormat = selfLink
300-
resource.ImportFormat = []string{selfLink}
301352
resource.CreateUrl = fmt.Sprintf("%s?%s={{%s}}", baseUrl, queryParam, google.Underscore(queryParam))
302-
resource.Description = "Description"
303353

304354
resource.AutogenAsync = true
305355
async := api.NewAsync()
@@ -325,28 +375,42 @@ func buildResource(filePath, resourceName string, in *resource, root *openapi3.T
325375
resource.Async.Actions = append(resource.Async.Actions, "delete")
326376
}
327377

378+
resource = attachStandardFunctionality(resource)
379+
380+
return resource
381+
}
382+
383+
// Standard functionality between regular and singleton resources
384+
func attachStandardFunctionality(resource api.Resource) api.Resource {
385+
resource.Description = "Description"
386+
387+
resource.IdFormat = resource.SelfLink
388+
resource.ImportFormat = []string{resource.SelfLink}
389+
328390
example := r.Examples{}
329391
example.Name = "name_of_example_file"
330392
example.PrimaryResourceId = "example"
331393
example.Vars = map[string]string{"resource_name": "test-resource"}
332394

333395
resource.Examples = []*r.Examples{&example}
334396

335-
resourceNameBytes := []byte(resourceName)
397+
resourceNameBytes := []byte(resource.Name)
336398
// Write the status as an encoded string to flag when a YAML file has been
337399
// copy and pasted without actually using this tool
338400
resource.AutogenStatus = base64.StdEncoding.EncodeToString(resourceNameBytes)
339401

340402
return resource
341403
}
342404

343-
func parseOpenApi(resourcePath, resourceName string, root *openapi3.T) []any {
405+
func parseOpenApi(resourcePath, resourceName string, op *openapi3.Operation) []any {
406+
if op == nil {
407+
log.Fatalf("error parsing nil OpenAPI operation for resource: %v", resourceName)
408+
}
344409
returnArray := []any{}
345-
path := root.Paths.Find(resourcePath)
346410

347411
parameters := []*api.Type{}
348412
var idParam string
349-
for _, param := range path.Post.Parameters {
413+
for _, param := range op.Parameters {
350414
if strings.Contains(strings.ToLower(param.Value.Name), strings.ToLower(resourceName)) {
351415
idParam = param.Value.Name
352416
}
@@ -357,7 +421,7 @@ func parseOpenApi(resourcePath, resourceName string, root *openapi3.T) []any {
357421
}
358422
paramObj.Description = trimDescription(description)
359423

360-
if param.Value.Name == "requestId" || param.Value.Name == "validateOnly" || paramObj.Name == "" {
424+
if param.Value.Name == "requestId" || param.Value.Name == "validateOnly" || paramObj.Name == "" || paramObj.Name == "updateMask" {
361425
continue
362426
}
363427

@@ -366,7 +430,7 @@ func parseOpenApi(resourcePath, resourceName string, root *openapi3.T) []any {
366430
parameters = append(parameters, &paramObj)
367431
}
368432

369-
properties := buildProperties(path.Post.RequestBody.Value.Content["application/json"].Schema.Value.Properties, path.Post.RequestBody.Value.Content["application/json"].Schema.Value.Required)
433+
properties := buildProperties(op.RequestBody.Value.Content["application/json"].Schema.Value.Properties, op.RequestBody.Value.Content["application/json"].Schema.Value.Required)
370434

371435
returnArray = append(returnArray, parameters)
372436
returnArray = append(returnArray, properties)

mmv1/openapi_generate/parser_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func TestFindResources(t *testing.T) {
4545
t.Fatalf("Could not validate data %s", err)
4646
}
4747
res := findResources(doc)
48-
if len(res) != 2 {
48+
if len(res) != 3 {
4949
t.Fatalf("Expected 2 resources, found: %d", len(res))
5050
}
5151
if !res["Food"].create.async {
@@ -54,4 +54,7 @@ func TestFindResources(t *testing.T) {
5454
if res["Pet"].create.async {
5555
t.Error("Pet resource is not supposed to be detected as async")
5656
}
57+
if res["Breeds"].update == nil {
58+
t.Error("Singleton update should be found")
59+
}
5760
}

mmv1/openapi_generate/test_data/test_api.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,57 @@ paths:
165165
application/json:
166166
schema:
167167
$ref: "#/components/schemas/Error"
168+
/animals/{animalId}/breeds:
169+
get:
170+
summary: Info for a specific breed
171+
operationId: showBreeds
172+
tags:
173+
- breed
174+
parameters:
175+
- name: animalId
176+
in: path
177+
required: true
178+
description: The id of the animal breeds to retrieve
179+
schema:
180+
type: string
181+
responses:
182+
200:
183+
description: Expected response to a valid request
184+
content:
185+
application/json:
186+
schema:
187+
$ref: "#/components/schemas/Breeds"
188+
default:
189+
description: unexpected error
190+
content:
191+
application/json:
192+
schema:
193+
$ref: "#/components/schemas/Error"
194+
patch:
195+
summary: Update breeds
196+
operationId: UpdateBreeds
197+
tags:
198+
- breeds
199+
parameters:
200+
- name: animalId
201+
in: path
202+
required: true
203+
description: The id of the animal breeds to update
204+
schema:
205+
type: string
206+
requestBody:
207+
description: "Required. The breed being updated"
208+
content:
209+
application/json:
210+
schema:
211+
$ref: "#/components/schemas/Breeds"
212+
responses:
213+
default:
214+
description: Result
215+
content:
216+
application/json:
217+
schema:
218+
$ref: "#/components/schemas/Breeds"
168219
components:
169220
schemas:
170221
Pet:
@@ -190,10 +241,20 @@ components:
190241
properties:
191242
name:
192243
type: string
244+
Breed:
245+
required:
246+
- name
247+
properties:
248+
name:
249+
type: string
193250
Foods:
194251
type: array
195252
items:
196253
$ref: "#/components/schemas/Food"
254+
Breeds:
255+
type: array
256+
items:
257+
$ref: "#/components/schemas/Breed"
197258
Pets:
198259
type: array
199260
items:

0 commit comments

Comments
 (0)