Repository: goadesign/goa
Pull request #2889 (2021-07-15 21:09:00)

Fix openapi extensions in v3
Add support for OpenAPI extensions via the `Meta` keyword to v3.


Patch:
diff --git a/http/codegen/openapi/v3/builder.go b/http/codegen/openapi/v3/builder.go
index 96bd743f..c2393424 100644
--- a/http/codegen/openapi/v3/builder.go
+++ b/http/codegen/openapi/v3/builder.go
@@ -57,6 +57,7 @@ func buildInfo(api *expr.APIExpr) *Info {
 		Description:    api.Description,
 		TermsOfService: api.TermsOfService,
 		Version:        ver,
+		Extensions:     openapi.ExtensionsFromExpr(api.Meta),
 	}
 	if c := api.Contact; c != nil {
 		info.Contact = &Contact{
@@ -108,6 +109,7 @@ func buildPaths(h *expr.HTTPExpr, bodies map[string]map[string]*EndpointBodies,
 			continue
 		}
 
+		exts := openapi.ExtensionsFromExpr(svc.Meta)
 		sbod := bodies[svc.Name()]
 
 		// endpoints
@@ -144,6 +146,12 @@ func buildPaths(h *expr.HTTPExpr, bodies map[string]map[string]*EndpointBodies,
 						path.Patch = operation
 					}
 					path.Extensions = openapi.ExtensionsFromExpr(r.Endpoint.Meta)
+					if len(exts) > 0 {
+						path.Extensions = make(map[string]interface{})
+						for k, v := range exts {
+							path.Extensions[k] = v
+						}
+					}
 				}
 			}
 		}
diff --git a/http/codegen/openapi/v3/openapi.go b/http/codegen/openapi/v3/openapi.go
index 448c9e21..0dfbfaf4 100644
--- a/http/codegen/openapi/v3/openapi.go
+++ b/http/codegen/openapi/v3/openapi.go
@@ -246,4 +246,72 @@ type (
 		Scopes           map[string]string      `json:"scopes" yaml:"scopes"`
 		Extensions       map[string]interface{} `json:"-" yaml:"-"`
 	}
+
+	// These types are used in openapi.MarshalJSON() to avoid recursive call of json.Marshal().
+	_Info           Info
+	_PathItem       PathItem
+	_Operation      Operation
+	_Parameter      Parameter
+	_Response       Response
+	_SecurityScheme SecurityScheme
 )
+
+// MarshalJSON returns the JSON encoding of i.
+func (i Info) MarshalJSON() ([]byte, error) {
+	return openapi.MarshalJSON(_Info(i), i.Extensions)
+}
+
+// MarshalJSON returns the JSON encoding of p.
+func (p PathItem) MarshalJSON() ([]byte, error) {
+	return openapi.MarshalJSON(_PathItem(p), p.Extensions)
+}
+
+// MarshalJSON returns the JSON encoding of o.
+func (o Operation) MarshalJSON() ([]byte, error) {
+	return openapi.MarshalJSON(_Operation(o), o.Extensions)
+}
+
+// MarshalJSON returns the JSON encoding of p.
+func (p Parameter) MarshalJSON() ([]byte, error) {
+	return openapi.MarshalJSON(_Parameter(p), p.Extensions)
+}
+
+// MarshalJSON returns the JSON encoding of r.
+func (r Response) MarshalJSON() ([]byte, error) {
+	return openapi.MarshalJSON(_Response(r), r.Extensions)
+}
+
+// MarshalJSON returns the JSON encoding of s.
+func (s SecurityScheme) MarshalJSON() ([]byte, error) {
+	return openapi.MarshalJSON(_SecurityScheme(s), s.Extensions)
+}
+
+// MarshalYAML returns value which marshaled in place of the original value
+func (i Info) MarshalYAML() (interface{}, error) {
+	return openapi.MarshalYAML(_Info(i), i.Extensions)
+}
+
+// MarshalYAML returns value which marshaled in place of the original value
+func (p PathItem) MarshalYAML() (interface{}, error) {
+	return openapi.MarshalYAML(_PathItem(p), p.Extensions)
+}
+
+// MarshalYAML returns value which marshaled in place of the original value
+func (o Operation) MarshalYAML() (interface{}, error) {
+	return openapi.MarshalYAML(_Operation(o), o.Extensions)
+}
+
+// MarshalYAML returns value which marshaled in place of the original value
+func (p Parameter) MarshalYAML() (interface{}, error) {
+	return openapi.MarshalYAML(_Parameter(p), p.Extensions)
+}
+
+// MarshalYAML returns value which marshaled in place of the original value
+func (r Response) MarshalYAML() (interface{}, error) {
+	return openapi.MarshalYAML(_Response(r), r.Extensions)
+}
+
+// MarshalYAML returns value which marshaled in place of the original value
+func (s SecurityScheme) MarshalYAML() (interface{}, error) {
+	return openapi.MarshalYAML(_SecurityScheme(s), s.Extensions)
+}


Test patch:
diff --git a/http/codegen/openapi/v3/testdata/golden/endpoint_file0.golden b/http/codegen/openapi/v3/testdata/golden/endpoint_file0.golden
index cad2c60c..0fb0803d 100644
--- a/http/codegen/openapi/v3/testdata/golden/endpoint_file0.golden
+++ b/http/codegen/openapi/v3/testdata/golden/endpoint_file0.golden
@@ -1,1 +1,1 @@
-{"openapi":"3.0.3","info":{"title":"Goa API","version":"1.0"},"servers":[{"url":"https://goa.design"}],"paths":{"/":{"post":{"tags":["testService"],"summary":"testEndpoint testService","operationId":"testService#testEndpoint","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestEndpointRequestBody"},"example":{"string":""}}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestEndpointRequestBody"},"example":{"string":""}}}}}}}},"components":{"schemas":{"TestEndpointRequestBody":{"type":"object","properties":{"string":{"example":"","type":"string","x-test-schema":"Payload"}},"example":{"string":""}}}},"tags":[{"name":"testService"}]}
\ No newline at end of file
+{"openapi":"3.0.3","info":{"title":"Goa API","version":"1.0","x-test-api":"API"},"servers":[{"url":"https://goa.design"}],"paths":{"/":{"post":{"operationId":"testService#testEndpoint","requestBody":{"content":{"application/json":{"example":{"string":""},"schema":{"$ref":"#/components/schemas/TestEndpointRequestBody"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"string":""},"schema":{"$ref":"#/components/schemas/TestEndpointRequestBody"}}},"description":"OK response."}},"summary":"testEndpoint testService","tags":["testService"],"x-test-operation":"Operation"},"x-test-foo":"bar"}},"components":{"schemas":{"TestEndpointRequestBody":{"type":"object","properties":{"string":{"example":"","type":"string","x-test-schema":"Payload"}},"example":{"string":""}}}},"tags":[{"name":"testService"}]}
\ No newline at end of file
diff --git a/http/codegen/openapi/v3/testdata/golden/endpoint_file1.golden b/http/codegen/openapi/v3/testdata/golden/endpoint_file1.golden
index 15c4e237..21ea5df4 100644
--- a/http/codegen/openapi/v3/testdata/golden/endpoint_file1.golden
+++ b/http/codegen/openapi/v3/testdata/golden/endpoint_file1.golden
@@ -2,32 +2,35 @@ openapi: 3.0.3
 info:
   title: Goa API
   version: "1.0"
+  x-test-api: API
 servers:
 - url: https://goa.design
 paths:
   /:
     post:
-      tags:
-      - testService
-      summary: testEndpoint testService
       operationId: testService#testEndpoint
       requestBody:
-        required: true
         content:
           application/json:
-            schema:
-              $ref: '#/components/schemas/TestEndpointRequestBody'
             example:
               string: ""
+            schema:
+              $ref: '#/components/schemas/TestEndpointRequestBody'
+        required: true
       responses:
         "200":
-          description: OK response.
           content:
             application/json:
-              schema:
-                $ref: '#/components/schemas/TestEndpointRequestBody'
               example:
                 string: ""
+              schema:
+                $ref: '#/components/schemas/TestEndpointRequestBody'
+          description: OK response.
+      summary: testEndpoint testService
+      tags:
+      - testService
+      x-test-operation: Operation
+    x-test-foo: bar
 components:
   schemas:
     TestEndpointRequestBody: