Repository: goadesign/goa (go)

Issue:
ResultType containing Map attribute with key of custom type generates properly but produces uncompilable code
Using the following example code:
```go
package design

import (
	. "goa.design/goa/v3/dsl"
)

var PropertyName = Type(
	"PropertyName", String, func() {
		Pattern(`^[a-z]{1,30}(_[0-9])?$`) // lowercase letters only - can optionally end in _digit (_0, _1)
	},
)
var Properties = Type("Properties", func() {
	Attribute("i", MapOf(PropertyName, String), func() {
		Description("Internal properties")
		Meta("struct:tag:json", "i,omitempty")
	})
})
var SomeType = Type("SomeType", func() {
	Attribute("properties", Properties, "xx properties")
})

var ResultWithMap = ResultType("application/vnd.xx.resultwithmap", func() {
	Reference(SomeType)
	Attribute("properties")
})

var _ = API("placeholder", func() {
	Title("placeholder Management Service")
	Description("Service for managing placeholder data")

	Server("api", func() {
		Host(
			"localhost", func() {
				URI("http://localhost:80")
			},
		)
	})
})

var _ = Service("placeholder", func() {
	Description("The placeholder service provides backend services...")

	Method("getitem", func() {
		Payload(func() {
			Attribute("something", String)
			Required("something")
		})
		Result(ResultWithMap)
		HTTP(
			func() {
				GET("/placeholder/{something}")
			},
		)
	})
})
```
produces the following build errors:
```go
gen/placeholder/views/view.go:73:50: cannot use k (type PropertyNameView) as type string in argument to goa.ValidatePattern
gen/placeholder/views/view.go:80:49: cannot use result (type PropertyNameView) as type string in argument to goa.ValidatePattern
```

Because these generated functioins:
```go
func ValidatePropertiesView(result *PropertiesView) (err error) {
	for k, _ := range result.I {
		err = goa.MergeErrors(err, goa.ValidatePattern("result.i.key", k, "^[a-z]{1,30}(_[0-9])?$"))
	}
	return
}

// ValidatePropertyNameView runs the validations defined on PropertyNameView.
func ValidatePropertyNameView(result PropertyNameView) (err error) {
	err = goa.MergeErrors(err, goa.ValidatePattern("result", result, "^[a-z]{1,30}(_[0-9])?$"))
	return
}
```

don't properly cast the key 'k' as string(k) in the calls to goa.ValidatePattern

Pull request description:
Properly handle primitive alias types
in validation code.
Fixes #2985

Patch:
diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go
index f5780119..87fdb61c 100644
--- a/codegen/service/service_data.go
+++ b/codegen/service/service_data.go
@@ -1479,7 +1479,7 @@ func buildValidations(projected *expr.AttributeExpr, scope *codegen.NameScope) [
 					})
 					ctx = projectedTypeContext("", !expr.IsPrimitive(projected.Type), scope)
 				}
-				data["Validate"] = codegen.RecursiveValidationCode(&expr.AttributeExpr{Type: o, Validation: rt.Validation}, ctx, true, "result")
+				data["Validate"] = codegen.RecursiveValidationCode(&expr.AttributeExpr{Type: o, Validation: rt.Validation}, ctx, true, false, "result")
 				data["Fields"] = fields
 			}
 
@@ -1504,7 +1504,7 @@ func buildValidations(projected *expr.AttributeExpr, scope *codegen.NameScope) [
 			Name:        name,
 			Description: fmt.Sprintf("%s runs the validations defined on %s.", name, tname),
 			Ref:         scope.GoTypeRef(projected),
-			Validate:    codegen.RecursiveValidationCode(ut.Attribute(), ctx, true, "result"),
+			Validate:    codegen.RecursiveValidationCode(ut.Attribute(), ctx, true, expr.IsAlias(ut), "result"),
 		})
 	}
 	return validations
diff --git a/codegen/validation.go b/codegen/validation.go
index 64836b43..ded6f8d3 100644
--- a/codegen/validation.go
+++ b/codegen/validation.go
@@ -53,16 +53,17 @@ func init() {
 //
 // req indicates whether the attribute is required (true) or optional (false)
 //
+// alias indicates whether the attribute is an alias user type attribute.
+//
 // target is the variable name against which the validation code is generated
 //
 // context is used to produce helpful messages in case of error.
 //
-func ValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, req bool, target, context string) string {
+func ValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, req, alias bool, target, context string) string {
 	validation := att.Validation
-	if validation == nil {
-		if ut, ok := att.Type.(expr.UserType); ok {
-			validation = ut.Attribute().Validation
-		}
+	ut, isut := att.Type.(expr.UserType)
+	if isut && validation == nil {
+		validation = ut.Attribute().Validation
 	}
 	if validation == nil {
 		return ""
@@ -76,6 +77,9 @@ func ValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, req bool,
 	if isPointer && expr.IsPrimitive(att.Type) && !isNativePointer {
 		tval = "*" + tval
 	}
+	if alias {
+		tval = fmt.Sprintf("%s(%s)", att.Type.Name(), tval)
+	}
 	data := map[string]interface{}{
 		"attribute": att,
 		"attCtx":    attCtx,
@@ -191,12 +195,12 @@ func ValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, req bool,
 // attCtx is the Attributor for the given attribute which is used to generate
 // attribute name and reference in the validation code.
 //
-func RecursiveValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, req bool, target string) string {
+func RecursiveValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, req, alias bool, target string) string {
 	seen := make(map[string]*bytes.Buffer)
-	return recurseValidationCode(att, attCtx, req, target, target, seen).String()
+	return recurseValidationCode(att, attCtx, req, alias, target, target, seen).String()
 }
 
-func recurseValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, req bool, target, context string, seen map[string]*bytes.Buffer) *bytes.Buffer {
+func recurseValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, req, alias bool, target, context string, seen map[string]*bytes.Buffer) *bytes.Buffer {
 	var (
 		buf   = new(bytes.Buffer)
 		first = true
@@ -211,22 +215,23 @@ func recurseValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, re
 	}
 
 	code := func(ctx *AttributeContext, att *expr.AttributeExpr, tgt, suf string) string {
-		if ut, ok := att.Type.(expr.UserType); ok {
-			if expr.IsPrimitive(ut.Attribute().Type) {
-				return recurseValidationCode(ut.Attribute(), ctx, true, tgt, context+suf, seen).String()
-			} else if hasValidations(attCtx, ut) {
-				var buf bytes.Buffer
-				name := attCtx.Scope.Name(att, ctx.Pkg, ctx.Pointer, ctx.UseDefault)
-				data := map[string]interface{}{"name": Goify(name, true), "target": tgt}
-				if err := userValT.Execute(&buf, data); err != nil {
-					panic(err) // bug
-				}
-				return fmt.Sprintf("if %s != nil {\n\t%s\n}", tgt, buf.String())
-			}
-		} else {
-			return recurseValidationCode(att, ctx, true, tgt, context+suf, seen).String()
+		ut, ok := att.Type.(expr.UserType)
+		if !ok {
+			return recurseValidationCode(att, ctx, true, false, tgt, context+suf, seen).String()
 		}
-		return ""
+		if expr.IsAlias(ut) {
+			return recurseValidationCode(ut.Attribute(), ctx, true, true, tgt, context+suf, seen).String()
+		}
+		if !hasValidations(attCtx, ut) {
+			return ""
+		}
+		var buf bytes.Buffer
+		name := attCtx.Scope.Name(att, ctx.Pkg, ctx.Pointer, ctx.UseDefault)
+		data := map[string]interface{}{"name": Goify(name, true), "target": tgt}
+		if err := userValT.Execute(&buf, data); err != nil {
+			panic(err) // bug
+		}
+		return fmt.Sprintf("if %s != nil {\n\t%s\n}", tgt, buf.String())
 	}
 
 	newline := func() {
@@ -238,7 +243,7 @@ func recurseValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, re
 	}
 
 	// Write validations on attribute if any.
-	validation := ValidationCode(att, attCtx, req, target, context)
+	validation := ValidationCode(att, attCtx, req, alias, target, context)
 	if validation != "" {
 		buf.WriteString(validation)
 		first = false
@@ -296,7 +301,13 @@ func recurseValidationCode(att *expr.AttributeExpr, attCtx *AttributeContext, re
 
 func recurseAttribute(att *expr.AttributeExpr, attCtx *AttributeContext, nat *expr.NamedAttributeExpr, target, context string, seen map[string]*bytes.Buffer) string {
 	var validation string
-	if ut, ok := nat.Attribute.Type.(expr.UserType); ok {
+	ut, isut := nat.Attribute.Type.(expr.UserType)
+	if !isut {
+		target := fmt.Sprintf("%s.%s", target, attCtx.Scope.Field(nat.Attribute, nat.Name, true))
+		context := fmt.Sprintf("%s.%s", context, nat.Name)
+		code := recurseValidationCode(nat.Attribute, attCtx, att.IsRequired(nat.Name), false, target, context, seen)
+		validation = code.String()
+	} else {
 		// We need to check empirically whether there are validations to be
 		// generated, we can't just generate and check whether something was
 		// generated to avoid infinite recursions.
@@ -334,7 +345,7 @@ func recurseAttribute(att *expr.AttributeExpr, attCtx *AttributeContext, nat *ex
 			var buf bytes.Buffer
 			tgt := fmt.Sprintf("%s.%s", target, attCtx.Scope.Field(nat.Attribute, nat.Name, true))
 			if expr.IsPrimitive(nat.Attribute.Type) {
-				buf.Write(recurseValidationCode(ut.Attribute(), attCtx, att.IsRequired(nat.Name), tgt, context, seen).Bytes())
+				buf.Write(recurseValidationCode(ut.Attribute(), attCtx, att.IsRequired(nat.Name), true, tgt, context, seen).Bytes())
 			} else {
 				name := attCtx.Scope.Name(nat.Attribute, attCtx.Pkg, attCtx.Pointer, attCtx.UseDefault)
 				if err := userValT.Execute(&buf, map[string]interface{}{"name": Goify(name, true), "target": tgt}); err != nil {
@@ -343,15 +354,6 @@ func recurseAttribute(att *expr.AttributeExpr, attCtx *AttributeContext, nat *ex
 			}
 			validation = buf.String()
 		}
-	} else {
-		validation += recurseValidationCode(
-			nat.Attribute,
-			attCtx,
-			att.IsRequired(nat.Name),
-			fmt.Sprintf("%s.%s", target, attCtx.Scope.Field(nat.Attribute, nat.Name, true)),
-			fmt.Sprintf("%s.%s", context, nat.Name),
-			seen,
-		).String()
 	}
 	if validation != "" {
 		if expr.IsObject(nat.Attribute.Type) {
diff --git a/expr/types.go b/expr/types.go
index ecd3112b..63879295 100644
--- a/expr/types.go
+++ b/expr/types.go
@@ -237,6 +237,13 @@ func IsPrimitive(dt DataType) bool {
 	}
 }
 
+// IsAlias returns true if the data type is a user type backed by a primitive
+// type (so call aliased type).
+func IsAlias(dt DataType) bool {
+	_, isut := dt.(UserType)
+	return isut && IsPrimitive(dt)
+}
+
 // Equal compares the types recursively and returns true if they are equal. Two
 // types are equal if:
 //
diff --git a/grpc/codegen/service_data.go b/grpc/codegen/service_data.go
index d4b8b32e..46612f0a 100644
--- a/grpc/codegen/service_data.go
+++ b/grpc/codegen/service_data.go
@@ -709,7 +709,7 @@ func addValidation(att *expr.AttributeExpr, sd *ServiceData, req bool) *Validati
 		}
 	}
 	ctx := protoBufTypeContext("", sd.Scope)
-	if def := codegen.RecursiveValidationCode(att, ctx, true, "message"); def != "" {
+	if def := codegen.RecursiveValidationCode(att, ctx, true, expr.IsAlias(att.Type), "message"); def != "" {
 		v := &ValidationData{
 			Name:    "Validate" + name,
 			Def:     def,
@@ -749,7 +749,7 @@ func collectValidations(att *expr.AttributeExpr, ctx *codegen.AttributeContext,
 		}
 		sd.validations = append(sd.validations, &ValidationData{
 			Name:    "Validate" + name,
-			Def:     codegen.RecursiveValidationCode(att, ctx, true, "message"),
+			Def:     codegen.RecursiveValidationCode(att, ctx, true, expr.IsAlias(att.Type), "message"),
 			ArgName: "message",
 			SrcName: name,
 			SrcRef:  protoBufGoFullTypeRef(att, sd.PkgName, sd.Scope),
@@ -1241,7 +1241,7 @@ func extractMetadata(a *expr.MappedAttributeExpr, service *expr.AttributeExpr, s
 				mp.KeyType.Type.Kind() == expr.StringKind &&
 				mp.ElemType.Type.Kind() == expr.ArrayKind &&
 				expr.AsArray(mp.ElemType.Type).ElemType.Type.Kind() == expr.StringKind,
-			Validate:     codegen.RecursiveValidationCode(c, ctx, required, varn),
+			Validate:     codegen.RecursiveValidationCode(c, ctx, required, expr.IsAlias(c.Type), varn),
 			DefaultValue: c.DefaultValue,
 			Example:      c.Example(expr.Root.API.Random()),
 		})
diff --git a/http/codegen/service_data.go b/http/codegen/service_data.go
index 3b41a003..233ddd91 100644
--- a/http/codegen/service_data.go
+++ b/http/codegen/service_data.go
@@ -661,7 +661,7 @@ func (d ServicesData) analyze(hs *expr.HTTPServiceExpr) *ServiceData {
 						var vcode string
 						if att.Validation != nil {
 							ctx := httpContext("", rd.Scope, true, false)
-							vcode = codegen.RecursiveValidationCode(att, ctx, true, name)
+							vcode = codegen.RecursiveValidationCode(att, ctx, true, expr.IsAlias(att.Type), name)
 						}
 						initArgs[j] = &InitArgData{
 							Ref: name,
@@ -1016,7 +1016,7 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 							Type:         pAtt.Type,
 							TypeName:     sd.Scope.GoTypeName(pAtt),
 							TypeRef:      sd.Scope.GoTypeRef(pAtt),
-							Validate:     codegen.RecursiveValidationCode(pAtt, httpsvrctx, required, varn),
+							Validate:     codegen.RecursiveValidationCode(pAtt, httpsvrctx, required, expr.IsAlias(pAtt.Type), varn),
 							DefaultValue: pAtt.DefaultValue,
 							Example:      pAtt.Example(expr.Root.API.Random()),
 						},
@@ -1116,8 +1116,8 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 			)
 			if ut, ok := body.(expr.UserType); ok {
 				if val := ut.Attribute().Validation; val != nil {
-					svcode = codegen.RecursiveValidationCode(ut.Attribute(), httpsvrctx, true, "body")
-					cvcode = codegen.RecursiveValidationCode(ut.Attribute(), httpclictx, true, "body")
+					svcode = codegen.RecursiveValidationCode(ut.Attribute(), httpsvrctx, true, expr.IsAlias(ut), "body")
+					cvcode = codegen.RecursiveValidationCode(ut.Attribute(), httpclictx, true, expr.IsAlias(ut), "body")
 				}
 			}
 			serverArgs = []*InitArgData{{
@@ -1250,7 +1250,7 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 							TypeRef:      uref,
 							Type:         uatt.Type,
 							Pointer:      sc.UsernamePointer,
-							Validate:     codegen.RecursiveValidationCode(uatt, httpsvrctx, sc.UsernameRequired, sc.UsernameAttr),
+							Validate:     codegen.RecursiveValidationCode(uatt, httpsvrctx, sc.UsernameRequired, expr.IsAlias(uatt.Type), sc.UsernameAttr),
 							Example:      uatt.Example(expr.Root.API.Random()),
 						},
 					}
@@ -1272,7 +1272,7 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData {
 							TypeRef:      pref,
 							Type:         patt.Type,
 							Pointer:      sc.PasswordPointer,
-							Validate:     codegen.RecursiveValidationCode(patt, httpsvrctx, sc.PasswordRequired, sc.PasswordAttr),
+							Validate:     codegen.RecursiveValidationCode(patt, httpsvrctx, sc.PasswordRequired, expr.IsAlias(patt.Type), sc.PasswordAttr),
 							Example:      patt.Example(expr.Root.API.Random()),
 						},
 					}
@@ -1584,7 +1584,7 @@ func buildResponses(e *expr.HTTPEndpointExpr, result *expr.AttributeExpr, viewed
 							var vcode string
 							if ut, ok := resp.Body.Type.(expr.UserType); ok {
 								if val := ut.Attribute().Validation; val != nil {
-									vcode = codegen.RecursiveValidationCode(ut.Attribute(), httpclictx, true, "body")
+									vcode = codegen.RecursiveValidationCode(ut.Attribute(), httpclictx, true, expr.IsAlias(ut), "body")
 								}
 							}
 							clientArgs = []*InitArgData{{
@@ -1964,7 +1964,7 @@ func buildRequestBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointExp
 				varname, svc.Name, e.Name())
 			if svr {
 				// generate validation code for unmarshaled type (server-side).
-				validateDef = codegen.RecursiveValidationCode(ut.Attribute(), httpctx, true, "body")
+				validateDef = codegen.RecursiveValidationCode(ut.Attribute(), httpctx, true, expr.IsAlias(ut), "body")
 				if validateDef != "" {
 					validateRef = fmt.Sprintf("err = Validate%s(&body)", varname)
 				}
@@ -1979,7 +1979,7 @@ func buildRequestBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointExp
 			}
 			varname = sd.Scope.GoTypeRef(body)
 			ctx := codegen.NewAttributeContext(false, false, !svr, "", sd.Scope)
-			validateRef = codegen.RecursiveValidationCode(body, ctx, true, "body")
+			validateRef = codegen.RecursiveValidationCode(body, ctx, true, expr.IsAlias(body.Type), "body")
 			desc = body.Description
 		}
 	}
@@ -2113,7 +2113,7 @@ func buildResponseBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointEx
 				varname, svc.Name, e.Name())
 			if !svr && view == nil {
 				// generate validation code for unmarshaled type (client-side).
-				validateDef = codegen.RecursiveValidationCode(body, httpctx, true, "body")
+				validateDef = codegen.RecursiveValidationCode(body, httpctx, true, expr.IsAlias(body.Type), "body")
 				if validateDef != "" {
 					target := "&body"
 					if expr.IsArray(ut) {
@@ -2130,12 +2130,12 @@ func buildResponseBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointEx
 			desc = fmt.Sprintf("%s is the type of the %q service %q endpoint HTTP response body.",
 				varname, svc.Name, e.Name())
 			def = goTypeDef(sd.Scope, body, !svr, svr)
-			validateRef = codegen.RecursiveValidationCode(body, httpctx, true, "body")
+			validateRef = codegen.RecursiveValidationCode(body, httpctx, true, expr.IsAlias(body.Type), "body")
 		} else {
 			// response body is a primitive type. They are used as non-pointers when
 			// encoding/decoding responses.
 			httpctx = httpContext("", sd.Scope, false, true)
-			validateRef = codegen.RecursiveValidationCode(body, httpctx, true, "body")
+			validateRef = codegen.RecursiveValidationCode(body, httpctx, true, expr.IsAlias(body.Type), "body")
 			varname = sd.Scope.GoTypeRef(body)
 			desc = body.Description
 		}
@@ -2281,7 +2281,7 @@ func extractPathParams(a *expr.MappedAttributeExpr, service *expr.AttributeExpr,
 					TypeName:     scope.GoTypeName(c),
 					TypeRef:      scope.GoTypeRef(c),
 					Pointer:      false,
-					Validate:     codegen.RecursiveValidationCode(c, ctx, true, varn),
+					Validate:     codegen.RecursiveValidationCode(c, ctx, true, expr.IsAlias(c.Type), varn),
 					DefaultValue: c.DefaultValue,
 					Example:      c.Example(expr.Root.API.Random()),
 				},
@@ -2340,7 +2340,7 @@ func extractQueryParams(a *expr.MappedAttributeExpr, service *expr.AttributeExpr
 					TypeName:     scope.GoTypeName(c),
 					TypeRef:      typeRef,
 					Pointer:      pointer,
-					Validate:     codegen.RecursiveValidationCode(c, ctx, required, varn),
+					Validate:     codegen.RecursiveValidationCode(c, ctx, required, expr.IsAlias(c.Type), varn),
 					DefaultValue: c.DefaultValue,
 					Example:      c.Example(expr.Root.API.Random()),
 				},
@@ -2402,7 +2402,7 @@ func extractHeaders(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svc
 					Required:     required,
 					Pointer:      pointer,
 					Type:         hattr.Type,
-					Validate:     codegen.RecursiveValidationCode(hattr, svcCtx, required, varn),
+					Validate:     codegen.RecursiveValidationCode(hattr, svcCtx, required, expr.IsAlias(hattr.Type), varn),
 					DefaultValue: hattr.DefaultValue,
 					Example:      hattr.Example(expr.Root.API.Random()),
 				},
@@ -2459,7 +2459,7 @@ func extractCookies(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svc
 					Required:     required,
 					Pointer:      pointer,
 					Type:         hattr.Type,
-					Validate:     codegen.RecursiveValidationCode(hattr, svcCtx, required, varn),
+					Validate:     codegen.RecursiveValidationCode(hattr, svcCtx, required, expr.IsAlias(hattr.Type), varn),
 					DefaultValue: hattr.DefaultValue,
 					Example:      hattr.Example(expr.Root.API.Random()),
 				},
@@ -2549,7 +2549,7 @@ func attributeTypeData(ut expr.UserType, req, ptr, server bool, rd *ServiceData)
 		if req || !req && !server {
 			// generate validations for responses client-side and for
 			// requests server-side and CLI
-			validate = codegen.RecursiveValidationCode(ut.Attribute(), hctx, true, "body")
+			validate = codegen.RecursiveValidationCode(ut.Attribute(), hctx, true, expr.IsAlias(ut), "body")
 		}
 		if validate != "" {
 			validateRef = fmt.Sprintf("err = Validate%s(v)", name)
diff --git a/http/codegen/websocket.go b/http/codegen/websocket.go
index 5c94d42b..41602dd8 100644
--- a/http/codegen/websocket.go
+++ b/http/codegen/websocket.go
@@ -127,7 +127,7 @@ func initWebSocketData(ed *EndpointData, e *expr.HTTPEndpointExpr, sd *ServiceDa
 							if ut, ok := body.(expr.UserType); ok {
 								if val := ut.Attribute().Validation; val != nil {
 									httpctx := httpContext("", sd.Scope, true, true)
-									svcode = codegen.RecursiveValidationCode(ut.Attribute(), httpctx, true, "body")
+									svcode = codegen.RecursiveValidationCode(ut.Attribute(), httpctx, true, expr.IsAlias(ut), "body")
 								}
 							}
 						}


Test patch:
diff --git a/codegen/service/testdata/views_code.go b/codegen/service/testdata/views_code.go
index 0934c228..5fe6abe5 100644
--- a/codegen/service/testdata/views_code.go
+++ b/codegen/service/testdata/views_code.go
@@ -793,8 +793,8 @@ func ValidateResult(result *Result) (err error) {
 // "default" view.
 func ValidateResultView(result *ResultView) (err error) {
 	for _, e := range result.T {
-		if !(e == "a" || e == "b") {
-			err = goa.MergeErrors(err, goa.InvalidEnumValueError("result.t[*]", e, []interface{}{"a", "b"}))
+		if !(string(e) == "a" || string(e) == "b") {
+			err = goa.MergeErrors(err, goa.InvalidEnumValueError("result.t[*]", string(e), []interface{}{"a", "b"}))
 		}
 	}
 	return
@@ -802,8 +802,8 @@ func ValidateResultView(result *ResultView) (err error) {
 
 // ValidateUserTypeView runs the validations defined on UserTypeView.
 func ValidateUserTypeView(result UserTypeView) (err error) {
-	if !(result == "a" || result == "b") {
-		err = goa.MergeErrors(err, goa.InvalidEnumValueError("result", result, []interface{}{"a", "b"}))
+	if !(string(result) == "a" || string(result) == "b") {
+		err = goa.MergeErrors(err, goa.InvalidEnumValueError("result", string(result), []interface{}{"a", "b"}))
 	}
 	return
 }
diff --git a/codegen/testdata/validation_code.go b/codegen/testdata/validation_code.go
index 2e7caafb..30bd250b 100644
--- a/codegen/testdata/validation_code.go
+++ b/codegen/testdata/validation_code.go
@@ -15,6 +15,16 @@ const (
 			err = goa.MergeErrors(err, goa.InvalidRangeError("target.integer", *target.Integer, 100, false))
 		}
 	}
+	if target.ExclusiveInteger != nil {
+		if *target.ExclusiveInteger < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_integer", *target.ExclusiveInteger, 1, true))
+		}
+	}
+	if target.ExclusiveInteger != nil {
+		if *target.ExclusiveInteger < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_integer", *target.ExclusiveInteger, 1, true))
+		}
+	}
 }
 `
 
@@ -37,6 +47,16 @@ const (
 			err = goa.MergeErrors(err, goa.InvalidRangeError("target.integer", *target.Integer, 100, false))
 		}
 	}
+	if target.ExclusiveInteger != nil {
+		if *target.ExclusiveInteger < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_integer", *target.ExclusiveInteger, 1, true))
+		}
+	}
+	if target.ExclusiveInteger != nil {
+		if *target.ExclusiveInteger < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_integer", *target.ExclusiveInteger, 1, true))
+		}
+	}
 }
 `
 
@@ -52,6 +72,16 @@ const (
 			err = goa.MergeErrors(err, goa.InvalidRangeError("target.integer", *target.Integer, 100, false))
 		}
 	}
+	if target.ExclusiveInteger != nil {
+		if *target.ExclusiveInteger < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_integer", *target.ExclusiveInteger, 1, true))
+		}
+	}
+	if target.ExclusiveInteger != nil {
+		if *target.ExclusiveInteger < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_integer", *target.ExclusiveInteger, 1, true))
+		}
+	}
 }
 `
 
@@ -69,6 +99,16 @@ const (
 			err = goa.MergeErrors(err, goa.InvalidRangeError("target.float64", *target.Float64, 100.1, false))
 		}
 	}
+	if target.ExclusiveFloat64 != nil {
+		if *target.ExclusiveFloat64 < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_float64", *target.ExclusiveFloat64, 1, true))
+		}
+	}
+	if target.ExclusiveFloat64 != nil {
+		if *target.ExclusiveFloat64 < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_float64", *target.ExclusiveFloat64, 1, true))
+		}
+	}
 }
 `
 
@@ -91,6 +131,16 @@ const (
 			err = goa.MergeErrors(err, goa.InvalidRangeError("target.float64", *target.Float64, 100.1, false))
 		}
 	}
+	if target.ExclusiveFloat64 != nil {
+		if *target.ExclusiveFloat64 < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_float64", *target.ExclusiveFloat64, 1, true))
+		}
+	}
+	if target.ExclusiveFloat64 != nil {
+		if *target.ExclusiveFloat64 < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_float64", *target.ExclusiveFloat64, 1, true))
+		}
+	}
 }
 `
 
@@ -106,6 +156,16 @@ const (
 			err = goa.MergeErrors(err, goa.InvalidRangeError("target.float64", *target.Float64, 100.1, false))
 		}
 	}
+	if target.ExclusiveFloat64 != nil {
+		if *target.ExclusiveFloat64 < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_float64", *target.ExclusiveFloat64, 1, true))
+		}
+	}
+	if target.ExclusiveFloat64 != nil {
+		if *target.ExclusiveFloat64 < 1 {
+			err = goa.MergeErrors(err, goa.InvalidRangeError("target.exclusive_float64", *target.ExclusiveFloat64, 1, true))
+		}
+	}
 }
 `
 
@@ -171,6 +231,36 @@ const (
 		err = goa.MergeErrors(err, goa.ValidateFormat("target.string", *target.String, goa.FormatDateTime))
 	}
 }
+`
+
+	AliasTypeValidationCode = `func Validate() (err error) {
+	if target.RequiredAlias != nil {
+		err = goa.MergeErrors(err, goa.ValidatePattern("target", string(*target.RequiredAlias), "^[A-z].*[a-z]$"))
+	}
+	if target.RequiredAlias != nil {
+		if utf8.RuneCountInString(string(*target.RequiredAlias)) < 1 {
+			err = goa.MergeErrors(err, goa.InvalidLengthError("target", string(*target.RequiredAlias), utf8.RuneCountInString(string(*target.RequiredAlias)), 1, true))
+		}
+	}
+	if target.RequiredAlias != nil {
+		if utf8.RuneCountInString(string(*target.RequiredAlias)) > 10 {
+			err = goa.MergeErrors(err, goa.InvalidLengthError("target", string(*target.RequiredAlias), utf8.RuneCountInString(string(*target.RequiredAlias)), 10, false))
+		}
+	}
+	if target.Alias != nil {
+		err = goa.MergeErrors(err, goa.ValidatePattern("target", string(*target.Alias), "^[A-z].*[a-z]$"))
+	}
+	if target.Alias != nil {
+		if utf8.RuneCountInString(string(*target.Alias)) < 1 {
+			err = goa.MergeErrors(err, goa.InvalidLengthError("target", string(*target.Alias), utf8.RuneCountInString(string(*target.Alias)), 1, true))
+		}
+	}
+	if target.Alias != nil {
+		if utf8.RuneCountInString(string(*target.Alias)) > 10 {
+			err = goa.MergeErrors(err, goa.InvalidLengthError("target", string(*target.Alias), utf8.RuneCountInString(string(*target.Alias)), 10, false))
+		}
+	}
+}
 `
 
 	UserTypeRequiredValidationCode = `func Validate() (err error) {
diff --git a/codegen/testdata/validation_types_dsl.go b/codegen/testdata/validation_types_dsl.go
index 4838f36a..0da452b9 100644
--- a/codegen/testdata/validation_types_dsl.go
+++ b/codegen/testdata/validation_types_dsl.go
@@ -15,6 +15,10 @@ var ValidationTypesDSL = func() {
 			Attribute("integer", Int64, func() {
 				Maximum(100)
 			})
+			Attribute("exclusive_integer", Int64, func() {
+				ExclusiveMinimum(1)
+				ExclusiveMaximum(100)
+			})
 			Required("required_integer")
 		})
 
@@ -29,6 +33,10 @@ var ValidationTypesDSL = func() {
 			Attribute("float64", Float64, func() {
 				Maximum(100.1)
 			})
+			Attribute("exclusive_float64", Float64, func() {
+				ExclusiveMinimum(1.0)
+				ExclusiveMaximum(100.1)
+			})
 			Required("required_float")
 		})
 
@@ -48,6 +56,18 @@ var ValidationTypesDSL = func() {
 			Required("required_string")
 		})
 
+		Alias = Type("Alias", String, func() {
+			MinLength(1)
+			MaxLength(10)
+			Pattern("^[A-z].*[a-z]$")
+		})
+
+		_ = Type("AliasType", func() {
+			Attribute("required_alias", Alias)
+			Attribute("alias", Alias)
+			Required("required_string")
+		})
+
 		_ = Type("UserType", func() {
 			Attribute("required_integer", IntegerT)
 			Attribute("default_string", StringT, func() {
diff --git a/codegen/validation_test.go b/codegen/validation_test.go
index e2bb1d44..440a8e3c 100644
--- a/codegen/validation_test.go
+++ b/codegen/validation_test.go
@@ -15,6 +15,7 @@ func TestRecursiveValidationCode(t *testing.T) {
 		integerT = root.UserType("Integer")
 		stringT  = root.UserType("String")
 		floatT   = root.UserType("Float")
+		aliasT   = root.UserType("AliasType")
 		userT    = root.UserType("UserType")
 		arrayUT  = root.UserType("ArrayUserType")
 		arrayT   = root.UserType("Array")
@@ -40,6 +41,7 @@ func TestRecursiveValidationCode(t *testing.T) {
 		{"string-required", stringT, true, false, false, testdata.StringRequiredValidationCode},
 		{"string-pointer", stringT, false, true, false, testdata.StringPointerValidationCode},
 		{"string-use-default", stringT, false, false, true, testdata.StringUseDefaultValidationCode},
+		{"alias-type", aliasT, true, false, false, testdata.AliasTypeValidationCode},
 		{"user-type-required", userT, true, false, false, testdata.UserTypeRequiredValidationCode},
 		{"user-type-pointer", userT, false, true, false, testdata.UserTypePointerValidationCode},
 		{"user-type-default", userT, false, false, true, testdata.UserTypeUseDefaultValidationCode},
@@ -58,7 +60,7 @@ func TestRecursiveValidationCode(t *testing.T) {
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
 			ctx := NewAttributeContext(c.Pointer, false, c.UseDefault, "", scope)
-			code := RecursiveValidationCode(&expr.AttributeExpr{Type: c.Type}, ctx, c.Required, "target")
+			code := RecursiveValidationCode(&expr.AttributeExpr{Type: c.Type}, ctx, c.Required, expr.IsAlias(c.Type), "target")
 			code = FormatTestCode(t, "package foo\nfunc Validate() (err error){\n"+code+"}")
 			if code != c.Code {
 				t.Errorf("invalid code, got:\n%s\ngot vs. expected:\n%s", code, Diff(t, code, c.Code))
diff --git a/expr/types_test.go b/expr/types_test.go
index a9091ca4..d7e8141c 100644
--- a/expr/types_test.go
+++ b/expr/types_test.go
@@ -500,6 +500,53 @@ func TestIsPrimitive(t *testing.T) {
 	}
 }
 
+func TestIsAlias(t *testing.T) {
+	var (
+		aliasUserType = &UserTypeExpr{
+			AttributeExpr: &AttributeExpr{
+				Type: String,
+			},
+		}
+		notAliasUserType = &UserTypeExpr{
+			AttributeExpr: &AttributeExpr{
+				Type: &Object{},
+			},
+		}
+		aliasResultType = &ResultTypeExpr{
+			UserTypeExpr: aliasUserType,
+		}
+		notAliasResultType = &ResultTypeExpr{
+			UserTypeExpr: notAliasUserType,
+		}
+	)
+	cases := map[string]struct {
+		dt       DataType
+		expected bool
+	}{
+		"alias user type": {
+			dt:       aliasUserType,
+			expected: true,
+		},
+		"not alias user type": {
+			dt:       notAliasUserType,
+			expected: false,
+		},
+		"alias result type": {
+			dt:       aliasResultType,
+			expected: true,
+		},
+		"not alias result type": {
+			dt:       notAliasResultType,
+			expected: false,
+		},
+	}
+	for k, tc := range cases {
+		if actual := IsAlias(tc.dt); tc.expected != actual {
+			t.Errorf("%s: got %#v, expected %#v", k, actual, tc.expected)
+		}
+	}
+}
+
 func TestPrimitiveIsCompatible(t *testing.T) {
 	var (
 		b    = bool(true)