Repository: goadesign/goa
Pull request #3275 (2023-04-07 18:44:19)

Support custom examples in ErrorResult
With this PR https://github.com/goadesign/goa/pull/3038 we lost the ability to set custom examples for ErrorResult. This PR fixes this issue so that any custom examples for ErrorResult is respected.

raphael: Looks great, thank you!

Patch:
diff --git a/expr/result_type.go b/expr/result_type.go
index ffcd24ac..b0d6c89a 100644
--- a/expr/result_type.go
+++ b/expr/result_type.go
@@ -53,15 +53,7 @@ var (
 			AttributeExpr: &AttributeExpr{
 				Type:        errorResultType,
 				Description: "Error response result type",
-				UserExamples: []*ExampleExpr{{
-					Summary: "BadRequest",
-					Value: Val{
-						"name":    "bad_request",
-						"id":      "3F1FKVRR",
-						"message": "Value of ID must be an integer",
-					},
-				}},
-				Validation: &ValidationExpr{Required: []string{"name", "id", "message", "temporary", "timeout", "fault"}},
+				Validation:  &ValidationExpr{Required: []string{"name", "id", "message", "temporary", "timeout", "fault"}},
 			},
 			TypeName: "error",
 		},
diff --git a/http/codegen/openapi/v3/builder.go b/http/codegen/openapi/v3/builder.go
index acb3faea..205d03ba 100644
--- a/http/codegen/openapi/v3/builder.go
+++ b/http/codegen/openapi/v3/builder.go
@@ -289,8 +289,10 @@ func buildOperation(key string, r *expr.RouteExpr, bodies *EndpointBodies, rand
 				desc += ": " + *resp.Description
 			}
 			resp.Description = &desc
-			for _, content := range resp.Content {
-				content.Example = nil
+			if er.Type == expr.ErrorResult && len(er.Response.Body.ExtractUserExamples()) == 0 {
+				for _, content := range resp.Content {
+					content.Example = nil
+				}
 			}
 			responses[strconv.Itoa(er.Response.StatusCode)] = &ResponseRef{Value: resp}
 		}


Test patch:
diff --git a/http/codegen/openapi/v3/files_test.go b/http/codegen/openapi/v3/files_test.go
index ed009932..47438f34 100644
--- a/http/codegen/openapi/v3/files_test.go
+++ b/http/codegen/openapi/v3/files_test.go
@@ -53,6 +53,8 @@ func TestFiles(t *testing.T) {
 		{"string", testdata.StringValidationDSL},
 		{"integer", testdata.IntValidationDSL},
 		{"array", testdata.ArrayValidationDSL},
+		// Error examples
+		{"error-examples", testdata.ErrorExamplesDSL},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
diff --git a/http/codegen/openapi/v3/testdata/golden/error-examples_file0.golden b/http/codegen/openapi/v3/testdata/golden/error-examples_file0.golden
new file mode 100644
index 00000000..8cd58749
--- /dev/null
+++ b/http/codegen/openapi/v3/testdata/golden/error-examples_file0.golden
@@ -0,0 +1,1 @@
+{"openapi":"3.0.3","info":{"title":"Goa API","version":"1.0"},"servers":[{"url":"http://localhost:80","description":"Default server for test api"}],"paths":{"/":{"get":{"tags":["Errors"],"summary":"Error Errors","operationId":"Errors#Error","responses":{"204":{"description":"No Content response."},"400":{"description":"bad_request: Bad Request response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"fault":false,"id":"foo","message":"request is invalid","name":"bad_request","temporary":false,"timeout":false}}}},"404":{"description":"not_found: Not Found response.","content":{"application/vnd.goa.error":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"custom: Conflict response.","content":{"application/vnd.goa.custom-error":{"schema":{"$ref":"#/components/schemas/CustomError"},"example":{"message":"error message","name":"custom"}}}}}}}},"components":{"schemas":{"CustomError":{"type":"object","properties":{"message":{"type":"string","example":"error message"},"name":{"type":"string","example":"custom"}},"example":{"message":"error message","name":"custom"},"required":["name","message"]},"Error":{"type":"object","properties":{"fault":{"type":"boolean","description":"Is the error a server-side fault?","example":true},"id":{"type":"string","description":"ID is a unique identifier for this particular occurrence of the problem.","example":"123abc"},"message":{"type":"string","description":"Message is a human-readable explanation specific to this occurrence of the problem.","example":"parameter 'p' must be an integer"},"name":{"type":"string","description":"Name is the name of this class of errors.","example":"bad_request"},"temporary":{"type":"boolean","description":"Is the error temporary?","example":true},"timeout":{"type":"boolean","description":"Is the error a timeout?","example":false}},"example":{"fault":true,"id":"123abc","message":"parameter 'p' must be an integer","name":"bad_request","temporary":true,"timeout":true},"required":["name","id","message","temporary","timeout","fault"]}}},"tags":[{"name":"Errors"}]}
\ No newline at end of file
diff --git a/http/codegen/openapi/v3/testdata/golden/error-examples_file1.golden b/http/codegen/openapi/v3/testdata/golden/error-examples_file1.golden
new file mode 100644
index 00000000..e100f340
--- /dev/null
+++ b/http/codegen/openapi/v3/testdata/golden/error-examples_file1.golden
@@ -0,0 +1,105 @@
+openapi: 3.0.3
+info:
+    title: Goa API
+    version: "1.0"
+servers:
+    - url: http://localhost:80
+      description: Default server for test api
+paths:
+    /:
+        get:
+            tags:
+                - Errors
+            summary: Error Errors
+            operationId: Errors#Error
+            responses:
+                "204":
+                    description: No Content response.
+                "400":
+                    description: 'bad_request: Bad Request response.'
+                    content:
+                        application/vnd.goa.error:
+                            schema:
+                                $ref: '#/components/schemas/Error'
+                            example:
+                                fault: false
+                                id: foo
+                                message: request is invalid
+                                name: bad_request
+                                temporary: false
+                                timeout: false
+                "404":
+                    description: 'not_found: Not Found response.'
+                    content:
+                        application/vnd.goa.error:
+                            schema:
+                                $ref: '#/components/schemas/Error'
+                "409":
+                    description: 'custom: Conflict response.'
+                    content:
+                        application/vnd.goa.custom-error:
+                            schema:
+                                $ref: '#/components/schemas/CustomError'
+                            example:
+                                message: error message
+                                name: custom
+components:
+    schemas:
+        CustomError:
+            type: object
+            properties:
+                message:
+                    type: string
+                    example: error message
+                name:
+                    type: string
+                    example: custom
+            example:
+                message: error message
+                name: custom
+            required:
+                - name
+                - message
+        Error:
+            type: object
+            properties:
+                fault:
+                    type: boolean
+                    description: Is the error a server-side fault?
+                    example: true
+                id:
+                    type: string
+                    description: ID is a unique identifier for this particular occurrence of the problem.
+                    example: 123abc
+                message:
+                    type: string
+                    description: Message is a human-readable explanation specific to this occurrence of the problem.
+                    example: parameter 'p' must be an integer
+                name:
+                    type: string
+                    description: Name is the name of this class of errors.
+                    example: bad_request
+                temporary:
+                    type: boolean
+                    description: Is the error temporary?
+                    example: true
+                timeout:
+                    type: boolean
+                    description: Is the error a timeout?
+                    example: false
+            example:
+                fault: true
+                id: 123abc
+                message: parameter 'p' must be an integer
+                name: bad_request
+                temporary: true
+                timeout: true
+            required:
+                - name
+                - id
+                - message
+                - temporary
+                - timeout
+                - fault
+tags:
+    - name: Errors
diff --git a/http/codegen/testdata/error_response_dsls.go b/http/codegen/testdata/error_response_dsls.go
index 3ca89af4..b43c63ad 100644
--- a/http/codegen/testdata/error_response_dsls.go
+++ b/http/codegen/testdata/error_response_dsls.go
@@ -216,3 +216,41 @@ var NoBodyErrorResponseWithContentTypeDSL = func() {
 		})
 	})
 }
+
+var ErrorExamplesDSL = func() {
+	var customType = ResultType("application/vnd.goa.custom-error", func() {
+		TypeName("CustomError")
+		Attribute("message", String, func() {
+			Example("error message")
+		})
+		ErrorName("name", String, func() {
+			Example("custom")
+		})
+		Required("name", "message")
+	})
+
+	var _ = Service("Errors", func() {
+		Method("Error", func() {
+			Error("not_found") // default example
+			Error("bad_request", func() {
+				Example("BadRequest example", func() {
+					Value(Val{
+						"id":        "foo",
+						"name":      "bad_request",
+						"message":   "request is invalid",
+						"fault":     false,
+						"timeout":   false,
+						"temporary": false,
+					})
+				})
+			})
+			Error("custom", customType)
+			HTTP(func() {
+				GET("/")
+				Response("not_found", StatusNotFound)
+				Response("bad_request", StatusBadRequest)
+				Response("custom", StatusConflict)
+			})
+		})
+	})
+}