Repository: goadesign/goa
Pull request #3580 (2024-08-25 08:48:42)

Generate validation code for required attributes in inline struct http bodies
It looks the validation code is missing.
I tried to fix the code but it might not be the best way.

raphael: This change makes sense, good catch!

Patch:
diff --git a/http/codegen/service_data.go b/http/codegen/service_data.go
index 5881b8cf..c1818960 100644
--- a/http/codegen/service_data.go
+++ b/http/codegen/service_data.go
@@ -1992,6 +1992,9 @@ func buildRequestBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointExp
 			}
 		}
 	} else {
+		// Generate validation code first because inline struct validation is removed.
+		ctx := codegen.NewAttributeContext(!expr.IsPrimitive(body.Type), false, !svr, "", sd.Scope)
+		validateRef = codegen.ValidationCode(body, nil, ctx, true, expr.IsAlias(body.Type), false, "body")
 		if svr && expr.IsObject(body.Type) {
 			// Body is an explicit object described in the design and in
 			// this case the GoTypeRef is an inline struct definition. We
@@ -2000,8 +2003,6 @@ func buildRequestBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointExp
 			body.Validation = nil
 		}
 		varname = sd.Scope.GoTypeRef(body)
-		ctx := codegen.NewAttributeContext(false, false, !svr, "", sd.Scope)
-		validateRef = codegen.ValidationCode(body, nil, ctx, true, expr.IsAlias(body.Type), false, "body")
 		desc = body.Description
 	}
 	var init *InitData


Test patch:
diff --git a/http/codegen/server_decode_test.go b/http/codegen/server_decode_test.go
index d6143b41..bd16aa86 100644
--- a/http/codegen/server_decode_test.go
+++ b/http/codegen/server_decode_test.go
@@ -151,6 +151,7 @@ func TestDecode(t *testing.T) {
 		{"decode-body-user-nested", testdata.PayloadBodyNestedUserDSL, testdata.PayloadBodyNestedUserDecodeCode},
 		{"decode-body-user-validate", testdata.PayloadBodyUserValidateDSL, testdata.PayloadBodyUserValidateDecodeCode},
 		{"decode-body-object", testdata.PayloadBodyObjectDSL, testdata.PayloadBodyObjectDecodeCode},
+		{"decode-body-object-required", testdata.PayloadBodyObjectRequiredDSL, testdata.PayloadBodyObjectRequiredDecodeCode},
 		{"decode-body-object-validate", testdata.PayloadBodyObjectValidateDSL, testdata.PayloadBodyObjectValidateDecodeCode},
 		{"decode-body-union", testdata.PayloadBodyUnionDSL, testdata.PayloadBodyUnionDecodeCode},
 		{"decode-body-union-validate", testdata.PayloadBodyUnionValidateDSL, testdata.PayloadBodyUnionValidateDecodeCode},
@@ -170,6 +171,7 @@ func TestDecode(t *testing.T) {
 		{"decode-body-primitive-array-string-validate", testdata.PayloadBodyPrimitiveArrayStringValidateDSL, testdata.PayloadBodyPrimitiveArrayStringValidateDecodeCode},
 		{"decode-body-primitive-array-bool-validate", testdata.PayloadBodyPrimitiveArrayBoolValidateDSL, testdata.PayloadBodyPrimitiveArrayBoolValidateDecodeCode},
 
+		{"decode-body-primitive-array-user-required", testdata.PayloadBodyPrimitiveArrayUserRequiredDSL, testdata.PayloadBodyPrimitiveArrayUserRequiredDecodeCode},
 		{"decode-body-primitive-array-user-validate", testdata.PayloadBodyPrimitiveArrayUserValidateDSL, testdata.PayloadBodyPrimitiveArrayUserValidateDecodeCode},
 		{"decode-body-primitive-field-array-user", testdata.PayloadBodyPrimitiveFieldArrayUserDSL, testdata.PayloadBodyPrimitiveFieldArrayUserDecodeCode},
 		{"decode-body-extend-primitive-field-array-user", testdata.PayloadExtendBodyPrimitiveFieldArrayUserDSL, testdata.PayloadBodyPrimitiveFieldArrayUserDecodeCode},
diff --git a/http/codegen/testdata/payload_decode_functions.go b/http/codegen/testdata/payload_decode_functions.go
index 35fbc65c..ef650720 100644
--- a/http/codegen/testdata/payload_decode_functions.go
+++ b/http/codegen/testdata/payload_decode_functions.go
@@ -3917,6 +3917,40 @@ func DecodeMethodBodyObjectRequest(mux goahttp.Muxer, decoder func(*http.Request
 }
 `
 
+var PayloadBodyObjectRequiredDecodeCode = `// DecodeMethodBodyObjectRequiredRequest returns a decoder for requests sent to
+// the ServiceBodyObjectRequired MethodBodyObjectRequired endpoint.
+func DecodeMethodBodyObjectRequiredRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) {
+	return func(r *http.Request) (any, error) {
+		var (
+			body struct {
+				B *string ` + "`" + `form:"b" json:"b" xml:"b"` + "`" + `
+			}
+			err error
+		)
+		err = decoder(r).Decode(&body)
+		if err != nil {
+			if err == io.EOF {
+				return nil, goa.MissingPayloadError()
+			}
+			var gerr *goa.ServiceError
+			if errors.As(err, &gerr) {
+				return nil, gerr
+			}
+			return nil, goa.DecodePayloadError(err.Error())
+		}
+		if body.B == nil {
+			err = goa.MergeErrors(err, goa.MissingFieldError("b", "body"))
+		}
+		if err != nil {
+			return nil, err
+		}
+		payload := NewMethodBodyObjectRequiredPayload(body)
+
+		return payload, nil
+	}
+}
+`
+
 var PayloadBodyObjectValidateDecodeCode = `// DecodeMethodBodyObjectValidateRequest returns a decoder for requests sent to
 // the ServiceBodyObjectValidate MethodBodyObjectValidate endpoint.
 func DecodeMethodBodyObjectValidateRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) {
@@ -3938,6 +3972,12 @@ func DecodeMethodBodyObjectValidateRequest(mux goahttp.Muxer, decoder func(*http
 			}
 			return nil, goa.DecodePayloadError(err.Error())
 		}
+		if body.B != nil {
+			err = goa.MergeErrors(err, goa.ValidatePattern("body.b", *body.B, "pattern"))
+		}
+		if err != nil {
+			return nil, err
+		}
 		payload := NewMethodBodyObjectValidatePayload(body)
 
 		return payload, nil
@@ -4440,6 +4480,43 @@ func DecodeMethodBodyPrimitiveArrayBoolValidateRequest(mux goahttp.Muxer, decode
 }
 `
 
+var PayloadBodyPrimitiveArrayUserRequiredDecodeCode = `// DecodeMethodBodyPrimitiveArrayUserRequiredRequest returns a decoder for
+// requests sent to the ServiceBodyPrimitiveArrayUserRequired
+// MethodBodyPrimitiveArrayUserRequired endpoint.
+func DecodeMethodBodyPrimitiveArrayUserRequiredRequest(mux goahttp.Muxer, decoder func(*http.Request) goahttp.Decoder) func(*http.Request) (any, error) {
+	return func(r *http.Request) (any, error) {
+		var (
+			body []*PayloadTypeRequestBody
+			err  error
+		)
+		err = decoder(r).Decode(&body)
+		if err != nil {
+			if err == io.EOF {
+				return nil, goa.MissingPayloadError()
+			}
+			var gerr *goa.ServiceError
+			if errors.As(err, &gerr) {
+				return nil, gerr
+			}
+			return nil, goa.DecodePayloadError(err.Error())
+		}
+		for _, e := range body {
+			if e != nil {
+				if err2 := ValidatePayloadTypeRequestBody(e); err2 != nil {
+					err = goa.MergeErrors(err, err2)
+				}
+			}
+		}
+		if err != nil {
+			return nil, err
+		}
+		payload := NewMethodBodyPrimitiveArrayUserRequiredPayloadType(body)
+
+		return payload, nil
+	}
+}
+`
+
 var PayloadBodyPrimitiveArrayUserValidateDecodeCode = `// DecodeMethodBodyPrimitiveArrayUserValidateRequest returns a decoder for
 // requests sent to the ServiceBodyPrimitiveArrayUserValidate
 // MethodBodyPrimitiveArrayUserValidate endpoint.
diff --git a/http/codegen/testdata/payload_dsls.go b/http/codegen/testdata/payload_dsls.go
index dfaa4c34..561ed6f8 100644
--- a/http/codegen/testdata/payload_dsls.go
+++ b/http/codegen/testdata/payload_dsls.go
@@ -1947,7 +1947,6 @@ var PayloadBodyStringValidateDSL = func() {
 				Attribute("b", String, func() {
 					Pattern("pattern")
 				})
-				Required("b")
 			})
 			HTTP(func() {
 				POST("/")
@@ -2039,9 +2038,9 @@ var PayloadBodyObjectDSL = func() {
 	})
 }
 
-var PayloadBodyObjectValidateDSL = func() {
-	Service("ServiceBodyObjectValidate", func() {
-		Method("MethodBodyObjectValidate", func() {
+var PayloadBodyObjectRequiredDSL = func() {
+	Service("ServiceBodyObjectRequired", func() {
+		Method("MethodBodyObjectRequired", func() {
 			Payload(func() {
 				Attribute("b", String)
 				Required("b")
@@ -2057,6 +2056,24 @@ var PayloadBodyObjectValidateDSL = func() {
 	})
 }
 
+var PayloadBodyObjectValidateDSL = func() {
+	Service("ServiceBodyObjectValidate", func() {
+		Method("MethodBodyObjectValidate", func() {
+			Payload(func() {
+				Attribute("b", String, func() {
+					Pattern("pattern")
+				})
+			})
+			HTTP(func() {
+				POST("/")
+				Body(func() {
+					Attribute("b", String)
+				})
+			})
+		})
+	})
+}
+
 var PayloadBodyUnionDSL = func() {
 	var Union = Type("Union", func() {
 		OneOf("Values", func() {
@@ -2342,12 +2359,26 @@ var PayloadBodyPrimitiveArrayBoolValidateDSL = func() {
 	})
 }
 
+var PayloadBodyPrimitiveArrayUserRequiredDSL = func() {
+	var PayloadType = Type("PayloadType", func() {
+		Attribute("a", String)
+		Required("a")
+	})
+	Service("ServiceBodyPrimitiveArrayUserRequired", func() {
+		Method("MethodBodyPrimitiveArrayUserRequired", func() {
+			Payload(ArrayOf(PayloadType))
+			HTTP(func() {
+				POST("/")
+			})
+		})
+	})
+}
+
 var PayloadBodyPrimitiveArrayUserValidateDSL = func() {
 	var PayloadType = Type("PayloadType", func() {
 		Attribute("a", String, func() {
 			Pattern("pattern")
 		})
-		Required("a")
 	})
 	Service("ServiceBodyPrimitiveArrayUserValidate", func() {
 		Method("MethodBodyPrimitiveArrayUserValidate", func() {