Repository: goadesign/goa
Pull request #3008 (2022-02-24 05:51:16)

Cleanup use of Meta
* Deprecate all "swagger:xxx" metadata in favor of "openapi:xxx"
* Deprecate "struct:error:name" in favor of new DSL function ErrorName


Patch:
diff --git a/dsl/attribute.go b/dsl/attribute.go
index 03fca88f..7b15eb64 100644
--- a/dsl/attribute.go
+++ b/dsl/attribute.go
@@ -222,7 +222,7 @@ func Default(def interface{}) {
 // a DSL the Value function can be used to provide the example value.
 //
 // If no example is explicitly provided in an attribute expression then a random
-// example is generated unless the "swagger:example" meta is set to "false".
+// example is generated unless the "openapi:example" meta is set to "false".
 // See Meta.
 //
 // Example must appear in a Attributes, Attribute, Params, Param, Headers or
diff --git a/dsl/error.go b/dsl/error.go
index 269d52ae..5cb1685a 100644
--- a/dsl/error.go
+++ b/dsl/error.go
@@ -103,6 +103,102 @@ func Error(name string, args ...interface{}) {
 	}
 }
 
+// ErrorName identifies the attribute of a custom error type used to select the
+// returned error response when multiple errors of that type are defined on the
+// same method. The value of the field identifies the error name as defined in
+// the design. This makes it possible to define distinct transport mappings for
+// the various errors (for example to return different HTTP status codes). There
+// must be one and exactly one attribute defined with ErrorName on types used to
+// define errors.
+//
+// ErrorName must appear in a Type or ResultType expression.
+//
+// ErrorName takes the same arguments as Attribute or Field.
+//
+// Example design:
+//
+//    // All the methods exposed by service MyService can return the errors
+//    // "internal_error" and "bad_request". Both errors have the same type
+//    // CustomErrorType. "internal_error" is mapped to HTTP status 500 and
+//    // "bad_request" is mapped to HTTP status 400.
+//    var _ = Service("MyService", func() {
+//        Error("internal_error", CustomErrorType)
+//        Error("bad_request", CustomErrorType)
+//        HTTP(func() {
+//            Response("internal_error", StatusInternalServerError)
+//            Response("bad_request", StatusBadRequest)
+//        })
+//
+//        Method("Method", func() {
+//	      Payload(String)
+//            HTTP(func() {
+//                GET("/")
+//            })
+//        })
+//    })
+//
+//    var CustomErrorType = Type("CustomError", func() {
+//        // The "name" attribute is used to select the error response.
+//        // name should be set to either "internal_error" or "bad_request" by
+//        // the service method returning the error.
+//        ErrorName("name", String, "Name of error.")
+//        Attribute("message", String, "Message of error.")
+//        Attribute("occurred_at", String, "Time error occurred.", func() {
+//            Format(FormatDateTime)
+//        })
+//        Required("name", "message", "occurred_at")
+//    })
+//
+// Example usage:
+//
+//    func (s *svc) Method(ctx context.Context, p string) error {
+//        // ...
+//        if err != nil {
+//             return &myservice.CustomError{
+//                 Name:       "internal_error", // HTTP response status is 500.
+//                 Message:    "Something went wrong",
+//                 OccurredAt: time.Now().Format(time.RFC3339),
+//             }
+//        }
+//        // ...
+//        return nil
+//    }
+//
+func ErrorName(args ...interface{}) {
+	if len(args) == 0 {
+		eval.IncompatibleDSL()
+		return
+	}
+	dsl, ok := args[len(args)-1].(func())
+	if ok {
+		args[len(args)-1] = func() {
+			dsl()
+			Meta("struct:error:name")
+		}
+	} else {
+		args = append(args, func() {
+			Meta("struct:error:name")
+		})
+	}
+	switch actual := args[0].(type) {
+	case string:
+		Attribute(actual, args[1:]...)
+	case int:
+		if len(args) == 1 {
+			eval.IncompatibleDSL()
+			return
+		}
+		name, ok := args[1].(string)
+		if !ok {
+			eval.InvalidArgError("name", args[1])
+			return
+		}
+		Field(actual, name, args[2:]...)
+	default:
+		eval.InvalidArgError("name or position", args[0])
+	}
+}
+
 // Temporary qualifies an error type as describing temporary (i.e. retryable)
 // errors.
 //
diff --git a/dsl/meta.go b/dsl/meta.go
index 186aec2d..29948eba 100644
--- a/dsl/meta.go
+++ b/dsl/meta.go
@@ -30,31 +30,7 @@ import (
 //        Meta("type:generate:force", "service1", "service2")
 //    })
 //
-// - "struct:error:name" identifies the attribute of a result type used to
-// select the returned error when multiple errors are defined on the same
-// method. The value of the field corresponding to the attribute with the
-// struct:error:name metadata is matched against the names of the method errors
-// as defined in the design. This makes it possible to define distinct transport
-// mappings for the various errors (for example to return different HTTP status
-// codes). There must be one and exactly one attribute with the
-// struct:error:name metadata defined on result types used to define error
-// results.
-//
-//    var CustomErrorType = ResultType("application/vnd.goa.error", func() {
-//        Attributes(func() {
-//            Attribute("message", String, "Error returned.", func() {
-//                Meta("struct:error:name")
-//            })
-//            Attribute("occurred_at", String, "Time error occurred.", func() {
-//                Format(FormatDateTime)
-//            })
-//        })
-//    })
-//
-//    var _ = Service("MyService", func() {
-//        Error("internal_error", CustomErrorType)
-//        Error("bad_request", CustomErrorType)
-//    })
+// - "struct:error:name" DEPRECATED, use ErrorName instead.
 //
 // - "struct:field:name" overrides the Go struct field name generated by default
 // by goa. Applicable to attributes only.
@@ -65,10 +41,12 @@ import (
 //        })
 //    })
 //
-// - "struct:field:type" overrides the Go struct field type specified in the design, with one caveat;
-// if the type would have been a pointer (such as its not Required) the new type will also be a pointer.
-// Applicable to attributes only. The import path of the type should be passed in as the second parameter, if needed.
-// If the default imported package name conflicts with another, you can override that as well with the third parameter.
+// - "struct:field:type" overrides the Go struct field type specified in the
+// design, with one caveat; if the type would have been a pointer (such as its
+// not Required) the new type will also be a pointer.  Applicable to attributes
+// only. The import path of the type should be passed in as the second
+// parameter, if needed.  If the default imported package name conflicts with
+// another, you can override that as well with the third parameter.
 //
 //    var MyType = Type("BigOleMessage", func() {
 //        Attribute("type", String, "Type of big payload")
@@ -82,7 +60,7 @@ import (
 //
 //
 // - "struct:tag:xxx" sets a generated Go struct field tag and overrides tags
-// that goa would otherwise set. If the metadata value is a slice then the
+// that Goa would otherwise set. If the metadata value is a slice then the
 // strings are joined with the space character as separator. Applicable to
 // attributes only.
 //
@@ -93,52 +71,62 @@ import (
 //        })
 //    })
 //
-// - "swagger:generate" specifies whether Swagger specification should be
+// - "swagger:generate" DEPRECATED, use "openapi:generate" instead.
+//
+// - "openapi:generate" specifies whether OpenAPI specification should be
 // generated. Defaults to true. Applicable to services, methods and file
 // servers.
 //
 //    var _ = Service("MyService", func() {
-//        Meta("swagger:generate", "false")
+//        Meta("openapi:generate", "false")
 //    })
 //
-// - "swagger:summary" sets the Swagger operation summary field. Applicable to
+// - "swagger:summary" DEPRECATED, use "openapi:summary" instead
+//
+// - "openapi:summary" sets the OpenAPI operation summary field. Applicable to
 // methods.
 //
 //    var _ = Service("MyService", func() {
 //        Method("MyMethod", func() {
-//               Meta("swagger:summary", "Summary of MyMethod")
+//               Meta("openapi:summary", "Summary of MyMethod")
 //        })
 //    })
 //
-// - "swagger:example" specifies whether to generate random example. Defaults to
+// - "swagger:example" DEPRECATED, use "openapi:example" instead
+//
+// - "openapi:example" specifies whether to generate random example. Defaults to
 // true. Applicable to API (applies to all attributes) or individual attributes.
 //
 //    var _ = API("MyAPI", func() {
-//        Meta("swagger:example", "false")
+//        Meta("openapi:example", "false")
 //    })
 //
-// - "swagger:tag:xxx" sets the Swagger object field tag xxx. Applicable to
+// - "swagger:tag:xxx" DEPRECATED, use "openapi:tag:xxx" instead
+//
+// - "openapi:tag:xxx" sets the OpenAPI object field tag xxx. Applicable to
 // services and methods.
 //
 //    var _ = Service("MyService", func() {
 //        Method("MyMethod", func() {
-//            Meta("swagger:tag:Backend")
-//            Meta("swagger:tag:Backend:desc", "Description of Backend")
-//            Meta("swagger:tag:Backend:url", "http://example.com")
-//            Meta("swagger:tag:Backend:url:desc", "See more docs here")
-//            Meta("swagger:tag:Backend:extension:x-data", `{"foo":"bar"}`)
+//            Meta("openapi:tag:Backend")
+//            Meta("openapi:tag:Backend:desc", "Description of Backend")
+//            Meta("openapi:tag:Backend:url", "http://example.com")
+//            Meta("openapi:tag:Backend:url:desc", "See more docs here")
+//            Meta("openapi:tag:Backend:extension:x-data", `{"foo":"bar"}`)
 //        })
 //    })
 //
-// - "swagger:extension:xxx" sets the Swagger extensions xxx. The value can be
-// any valid JSON. Applicable to API (Swagger info and tag objects), Service
-// (Swagger paths object), Method (Swagger path-item object), Route (Swagger
-// operation object), Param (Swagger parameter object), Response (Swagger
-// response object) and Security (Swagger security-scheme object). See
+// - "swagger:extension:xxx" DEPRECATED, use "openapi:extension:xxx" instead
+//
+// - "openapi:extension:xxx" sets the OpenAPI extensions xxx. The value can be
+// any valid JSON. Applicable to API (OpenAPI info and tag objects), Service
+// (OpenAPI paths object), Method (OpenAPI path-item object), Route (OpenAPI
+// operation object), Param (OpenAPI parameter object), Response (OpenAPI
+// response object) and Security (OpenAPI security-scheme object). See
 // https://github.com/OAI/OpenAPI-Specification/blob/master/guidelines/EXTENSIONS.md.
 //
 //    var _ = API("MyAPI", func() {
-//        Meta("swagger:extension:x-api", `{"foo":"bar"}`)
+//        Meta("openapi:extension:x-api", `{"foo":"bar"}`)
 //    })
 //
 func Meta(name string, value ...string) {
diff --git a/expr/example.go b/expr/example.go
index 4711bbbb..57958a5c 100644
--- a/expr/example.go
+++ b/expr/example.go
@@ -25,7 +25,11 @@ func (a *AttributeExpr) Example(r *Random) interface{} {
 		return ex[len(ex)-1].Value
 	}
 
-	if value, ok := a.Meta.Last("swagger:example"); ok && value == "false" {
+	value, ok := a.Meta.Last("openapi:example")
+	if !ok {
+		value, ok = a.Meta.Last("swagger:example")
+	}
+	if ok && value == "false" {
 		return nil
 	}
 
diff --git a/expr/method.go b/expr/method.go
index b2b3f0f3..536aeac9 100644
--- a/expr/method.go
+++ b/expr/method.go
@@ -207,7 +207,7 @@ func (m *MethodExpr) Validate() error {
 					return nil
 				})
 				if !found {
-					verr.Add(e, "type %q is used to define multiple errors and must identify the attribute containing error name. Use Meta with the key 'struct:error:name' on the error name attribute", e.AttributeExpr.Type.Name())
+					verr.Add(e, "type %q is used to define multiple errors and must identify the attribute containing the error name with ErrorName", e.AttributeExpr.Type.Name())
 					break
 				}
 			}
diff --git a/expr/service.go b/expr/service.go
index db282491..f2882c12 100644
--- a/expr/service.go
+++ b/expr/service.go
@@ -103,14 +103,14 @@ func (e *ErrorExpr) Validate() error {
 	walkAttribute(e.AttributeExpr, func(name string, att *AttributeExpr) error {
 		if _, ok := att.Meta["struct:error:name"]; ok {
 			if errField != "" {
-				verr.Add(e, "attribute %q has 'struct:error:name' meta which is already set for attribute %q in %q type", name, errField, e.AttributeExpr.Type.Name())
+				verr.Add(e, "duplicate error names in type %q", e.AttributeExpr.Type.Name())
 			}
 			errField = name
 			if att.Type != String {
-				verr.Add(e, "attribute %q with 'struct:error:name' in the meta must be a string in %q type", name, e.AttributeExpr.Type.Name())
+				verr.Add(e, "error name %q must be a string in type %q", name, e.AttributeExpr.Type.Name())
 			}
 			if !e.AttributeExpr.IsRequired(name) {
-				verr.Add(e, "attribute %q with 'struct:error:name' in the meta must be required in %q type", name, e.AttributeExpr.Type.Name())
+				verr.Add(e, "error name %q must be required in type %q", name, e.AttributeExpr.Type.Name())
 			}
 		}
 		return nil
diff --git a/http/codegen/openapi/extensions.go b/http/codegen/openapi/extensions.go
index 1ba09e7c..2b051347 100644
--- a/http/codegen/openapi/extensions.go
+++ b/http/codegen/openapi/extensions.go
@@ -7,13 +7,24 @@ import (
 	"goa.design/goa/v3/expr"
 )
 
-// ExtensionsFromExpr generates swagger extensions from the given meta
+// ExtensionsFromExpr generates openapi extensions from the given meta
 // expression.
 func ExtensionsFromExpr(mdata expr.MetaExpr) map[string]interface{} {
-	return extensionsFromExprWithPrefix(mdata, "swagger:extension:")
+	swag := extensionsFromExprWithPrefix(mdata, "swagger:extension:")
+	open := extensionsFromExprWithPrefix(mdata, "openapi:extension:")
+	if swag == nil {
+		return open
+	}
+	if open == nil {
+		return swag
+	}
+	for k, v := range open {
+		swag[k] = v
+	}
+	return swag
 }
 
-// extensionsFromExprWithPrefix generates swagger extensions from
+// extensionsFromExprWithPrefix generates openapi extensions from
 // the given meta expression with keys starting the given prefix.
 func extensionsFromExprWithPrefix(mdata expr.MetaExpr, prefix string) map[string]interface{} {
 	if !strings.HasSuffix(prefix, ":") {
diff --git a/http/codegen/openapi/json_schema.go b/http/codegen/openapi/json_schema.go
index 0339da3f..ea0fcb99 100644
--- a/http/codegen/openapi/json_schema.go
+++ b/http/codegen/openapi/json_schema.go
@@ -50,7 +50,7 @@ type (
 		// Union
 		AnyOf []*Schema `json:"anyOf,omitempty" yaml:"anyOf,omitempty"`
 
-		// Extensions defines the swagger extensions.
+		// Extensions defines the OpenAPI extensions.
 		Extensions map[string]interface{} `json:"-" yaml:"-"`
 	}
 
@@ -95,7 +95,7 @@ const (
 	Object = "object"
 	// String represents a JSON string.
 	String = "string"
-	// File is an extension used by Swagger to represent a file download.
+	// File is an extension used by OpenAPI to represent a file download.
 	File = "file"
 )
 
diff --git a/http/codegen/openapi/tags.go b/http/codegen/openapi/tags.go
index ed8af341..3b2b41f6 100644
--- a/http/codegen/openapi/tags.go
+++ b/http/codegen/openapi/tags.go
@@ -18,11 +18,11 @@ type Tag struct {
 	Description string `json:"description,omitempty" yaml:"description,omitempty"`
 	// ExternalDocs is additional external documentation for this tag.
 	ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
-	// Extensions defines the swagger extensions.
+	// Extensions defines the OpenAPI extensions.
 	Extensions map[string]interface{} `json:"-" yaml:"-"`
 }
 
-// TagsFromExpr extracts the swagger related metadata from the given expression.
+// TagsFromExpr extracts the OpenAPI related metadata from the given expression.
 func TagsFromExpr(mdata expr.MetaExpr) (tags []*Tag) {
 	var keys []string
 	for k := range mdata {
@@ -34,7 +34,7 @@ func TagsFromExpr(mdata expr.MetaExpr) (tags []*Tag) {
 		if len(chunks) != 3 {
 			continue
 		}
-		if chunks[0] != "swagger" || chunks[1] != "tag" {
+		if (chunks[0] != "swagger" && chunks[0] != "openapi") || chunks[1] != "tag" {
 			continue
 		}
 
@@ -73,7 +73,7 @@ func TagsFromExpr(mdata expr.MetaExpr) (tags []*Tag) {
 	return
 }
 
-// TagNamesFromExpr computes the names of the swagger tags specified in the
+// TagNamesFromExpr computes the names of the OpenAPI tags specified in the
 // given metadata expressions.
 func TagNamesFromExpr(mdatas ...expr.MetaExpr) (tagNames []string) {
 	for _, mdata := range mdatas {
diff --git a/http/codegen/openapi/v2/builder.go b/http/codegen/openapi/v2/builder.go
index 9a8a60fa..8da8b993 100644
--- a/http/codegen/openapi/v2/builder.go
+++ b/http/codegen/openapi/v2/builder.go
@@ -118,10 +118,12 @@ func defaultURI(h *expr.HostExpr) string {
 // mustGenerate returns true if the meta indicates that a OpenAPI specification should be
 // generated, false otherwise.
 func mustGenerate(meta expr.MetaExpr) bool {
-	if m, ok := meta["swagger:generate"]; ok {
-		if len(m) > 0 && m[0] == "false" {
-			return false
-		}
+	m, ok := meta.Last("openapi:generate")
+	if !ok {
+		m, ok = meta.Last("swagger:generate")
+	}
+	if ok && m == "false" {
+		return false
 	}
 	return true
 }
@@ -243,12 +245,12 @@ func hasAbsoluteRoutes(root *expr.RootExpr) bool {
 
 func summaryFromExpr(name string, e *expr.HTTPEndpointExpr) string {
 	for n, mdata := range e.Meta {
-		if n == "swagger:summary" && len(mdata) > 0 {
+		if (n == "openapi:summary" || n == "swagger:summary") && len(mdata) > 0 {
 			return mdata[0]
 		}
 	}
 	for n, mdata := range e.MethodExpr.Meta {
-		if n == "swagger:summary" && len(mdata) > 0 {
+		if (n == "openapi:summary" || n == "swagger:summary") && len(mdata) > 0 {
 			return mdata[0]
 		}
 	}
@@ -257,7 +259,7 @@ func summaryFromExpr(name string, e *expr.HTTPEndpointExpr) string {
 
 func summaryFromMeta(name string, meta expr.MetaExpr) string {
 	for n, mdata := range meta {
-		if n == "swagger:summary" && len(mdata) > 0 {
+		if (n == "openapi:summary" || n == "swagger:summary") && len(mdata) > 0 {
 			return mdata[0]
 		}
 	}
diff --git a/http/codegen/openapi/v3/builder.go b/http/codegen/openapi/v3/builder.go
index c2393424..0d77770a 100644
--- a/http/codegen/openapi/v3/builder.go
+++ b/http/codegen/openapi/v3/builder.go
@@ -200,17 +200,17 @@ func buildOperation(key string, r *expr.RouteExpr, bodies *EndpointBodies, rand
 		}
 	}
 
-	// swagger summary
+	// OpenAPI summary
 	var summary string
 	{
 		summary = fmt.Sprintf("%s %s", e.Name(), svc.Name())
 		for n, mdata := range r.Endpoint.Meta {
-			if n == "swagger:summary" && len(mdata) > 0 {
+			if (n == "openapi:summary" || n == "swagger:summary") && len(mdata) > 0 {
 				summary = mdata[0]
 			}
 		}
 		for n, mdata := range m.Meta {
-			if n == "swagger:summary" && len(mdata) > 0 {
+			if (n == "openapi:summary" || n == "swagger:summary") && len(mdata) > 0 {
 				summary = mdata[0]
 			}
 		}
@@ -340,12 +340,12 @@ func buildFileServerOperation(key string, fs *expr.HTTPFileServerExpr, api *expr
 		}
 	}
 
-	// swagger summary
+	// OpenAPI summary
 	var summary string
 	{
 		summary = fmt.Sprintf("Download %s", fs.FilePath)
 		for n, mdata := range fs.Meta {
-			if n == "swagger:summary" && len(mdata) > 0 {
+			if (n == "openapi:summary" || n == "swagger:summary") && len(mdata) > 0 {
 				summary = mdata[0]
 			}
 		}
@@ -582,10 +582,12 @@ func buildTags(api *expr.APIExpr) []*openapi.Tag {
 // mustGenerate returns true if the meta indicates that a OpenAPI specification should be
 // generated, false otherwise.
 func mustGenerate(meta expr.MetaExpr) bool {
-	if m, ok := meta["swagger:generate"]; ok {
-		if len(m) > 0 && m[0] == "false" {
-			return false
-		}
+	m, ok := meta.Last("openapi:generate")
+	if !ok {
+		m, ok = meta.Last("swagger:generate")
+	}
+	if ok && m == "false" {
+		return false
 	}
 	return true
 }


Test patch:
diff --git a/codegen/service/testdata/service_dsls.go b/codegen/service/testdata/service_dsls.go
index b61c128d..0f1ab2e0 100644
--- a/codegen/service/testdata/service_dsls.go
+++ b/codegen/service/testdata/service_dsls.go
@@ -135,9 +135,7 @@ var CustomErrorsDSL = func() {
 	var Result = ResultType("application/vnd.goa.error", func() {
 		TypeName("Result")
 		Attribute("a", String)
-		Attribute("b", String, func() {
-			Meta("struct:error:name")
-		})
+		ErrorName("b", String)
 		Required("b")
 	})
 	Service("CustomErrors", func() {
@@ -151,8 +149,7 @@ var CustomErrorsDSL = func() {
 
 var CustomErrorsCustomFieldDSL = func() {
 	var Result = ResultType("application/vnd.goa.error", func() {
-		Attribute("error", String, func() {
-			Meta("struct:error:name")
+		ErrorName("error", String, func() {
 			Meta("struct:field:name", "ErrorCode")
 		})
 		Required("error")
diff --git a/dsl/meta_test.go b/dsl/meta_test.go
index 8c6b69aa..bb9e6f24 100644
--- a/dsl/meta_test.go
+++ b/dsl/meta_test.go
@@ -16,7 +16,7 @@ func TestMetaData(t *testing.T) {
 		MetaFunc    func(e eval.Expression) expr.MetaExpr
 		Invocations int
 	}{
-		"userType":   {&expr.UserTypeExpr{AttributeExpr: &expr.AttributeExpr{}}, "swagger:summary", []string{"Short summary of what endpoint does"}, userTypeMeta, 1},
+		"userType":   {&expr.UserTypeExpr{AttributeExpr: &expr.AttributeExpr{}}, "openapi:summary", []string{"Short summary of what endpoint does"}, userTypeMeta, 1},
 		"api":        {&expr.APIExpr{}, "metadata", []string{"some metadata"}, apiExprMeta, 2},
 		"attribute":  {&expr.AttributeExpr{}, "attribute_meta", []string{"attr meta", "more attr meta"}, attributeMeta, 2},
 		"method":     {&expr.MethodExpr{Name: "testmethod"}, "method", []string{"method meta"}, methodMeta, 2},
diff --git a/expr/service_test.go b/expr/service_test.go
index 2dc34251..dfce8c6e 100644
--- a/expr/service_test.go
+++ b/expr/service_test.go
@@ -93,7 +93,7 @@ func TestServiceExprValidate(t *testing.T) {
 		DSL   func()
 		Error string
 	}{
-		{"service errors", testdata.ServiceErrorDSL, `attribute: attribute "a" with 'struct:error:name' in the meta must be required in "ServiceError" type`},
+		{"service errors", testdata.ServiceErrorDSL, `attribute: error name "a" must be required in type "ServiceError"`},
 	}
 
 	for _, tc := range cases {
@@ -114,12 +114,12 @@ func TestErrorExprValidate(t *testing.T) {
 	}{
 		{"no error", testdata.ValidErrorsDSL, ""},
 		{"invalid-struct-error-name-meta", testdata.InvalidStructErrorNameDSL,
-			`attribute: attribute "a" with 'struct:error:name' in the meta must be required in "ServiceError" type
-attribute: attribute "a" has 'struct:error:name' meta which is already set for attribute "b" in "Error" type
-attribute: attribute "a" with 'struct:error:name' in the meta must be a string in "Error" type
-attribute: attribute "a" with 'struct:error:name' in the meta must be required in "Error" type
-attribute: type "ErrorType" is used to define multiple errors and must identify the attribute containing error name. Use Meta with the key 'struct:error:name' on the error name attribute
-attribute: type "ErrorType" is used to define multiple errors and must identify the attribute containing error name. Use Meta with the key 'struct:error:name' on the error name attribute`,
+			`attribute: error name "a" must be required in type "ServiceError"
+attribute: duplicate error names in type "Error"
+attribute: error name "a" must be a string in type "Error"
+attribute: error name "a" must be required in type "Error"
+attribute: type "ErrorType" is used to define multiple errors and must identify the attribute containing the error name with ErrorName
+attribute: type "ErrorType" is used to define multiple errors and must identify the attribute containing the error name with ErrorName`,
 		},
 	}
 	for _, tc := range cases {
diff --git a/expr/testdata/example_test_dsls.go b/expr/testdata/example_test_dsls.go
index f81b19eb..8e6fde1b 100644
--- a/expr/testdata/example_test_dsls.go
+++ b/expr/testdata/example_test_dsls.go
@@ -100,7 +100,7 @@ var HidingExampleDSL = func() {
 	Service("HidingExample", func() {
 		Method("Method", func() {
 			Payload(String, func() {
-				Meta("swagger:example", "false")
+				Meta("openapi:example", "false")
 			})
 		})
 	})
@@ -108,7 +108,7 @@ var HidingExampleDSL = func() {
 
 var OverridingHiddenExamplesDSL = func() {
 	Service("OverridingHiddenExamples", func() {
-		Meta("swagger:example", "false")
+		Meta("openapi:example", "false")
 		Method("Method", func() {
 			Payload(String, func() {
 				Example("example")
diff --git a/expr/testdata/service_validate_dsls.go b/expr/testdata/service_validate_dsls.go
index fb3eadc6..6f0085ba 100644
--- a/expr/testdata/service_validate_dsls.go
+++ b/expr/testdata/service_validate_dsls.go
@@ -7,9 +7,7 @@ var ValidErrorsDSL = func() {
 		TypeName("Result")
 		Attributes(func() {
 			Attribute("a", String)
-			Attribute("b", String, func() {
-				Meta("struct:error:name")
-			})
+			ErrorName("b")
 			Required("b")
 		})
 	})
@@ -29,26 +27,20 @@ var ValidErrorsDSL = func() {
 
 var InvalidStructErrorNameDSL = func() {
 	var Common = Type("Common", func() {
-		Attribute("a", Int, func() { // invalid type for struct:error:name
-			Meta("struct:error:name")
-		})
+		ErrorName("a", Int) // invalid type for error name
 		Required("a")
 	})
 	var Result = ResultType("application/vnd.goa.error", func() {
 		TypeName("Error")
 		Attributes(func() {
 			Extend(Common)
-			Attribute("b", String, func() {
-				Meta("struct:error:name") // invalid duplication of struct:error:name
-			})
+			ErrorName("b") // invalid duplication of error name
 			Required("b")
 		})
 	})
 	var ServiceError = Type("ServiceError", func() {
-		Attribute("a", String, func() {
-			Meta("struct:error:name")
-		})
-		// invalid: attribute "a" with struct:error:name must be required
+		ErrorName("a")
+		// invalid: error name "a" must be required
 	})
 	var ErrorType = Type("ErrorType", func() {
 		Attribute("a", String)
@@ -65,10 +57,8 @@ var InvalidStructErrorNameDSL = func() {
 
 var ServiceErrorDSL = func() {
 	var ServiceError = Type("ServiceError", func() {
-		Attribute("a", String, func() {
-			Meta("struct:error:name")
-		})
-		// invalid: attribute "a" with struct:error:name must be required
+		ErrorName("a")
+		// invalid: error name "a" must be required
 	})
 	Service("InvalidStructErrorName", func() {
 		Error("service_error", ServiceError)
diff --git a/grpc/codegen/testdata/dsls.go b/grpc/codegen/testdata/dsls.go
index b94cbf81..376db68c 100644
--- a/grpc/codegen/testdata/dsls.go
+++ b/grpc/codegen/testdata/dsls.go
@@ -59,8 +59,7 @@ var UnaryRPCWithErrorsDSL = func() {
 	var AnotherError = ResultType("application/vnd.goa.another_error", func() {
 		TypeName("AnotherError")
 		Attributes(func() {
-			Attribute("name", String, func() {
-				Meta("struct:error:name")
+			ErrorName("name", String, func() {
 				Enum("this", "that")
 			})
 			Attribute("description", String)
diff --git a/http/codegen/openapi/v3/files_test.go b/http/codegen/openapi/v3/files_test.go
index 3e48f945..e0255933 100644
--- a/http/codegen/openapi/v3/files_test.go
+++ b/http/codegen/openapi/v3/files_test.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"path/filepath"
+	"strings"
 	"testing"
 	"text/template"
 
@@ -31,6 +32,7 @@ func TestFiles(t *testing.T) {
 	}{
 		// TestSections
 		{"file-service", testdata.FileServiceDSL},
+		{"file-service-swagger", testdata.FileServiceSwaggerDSL},
 		{"valid", testdata.SimpleDSL},
 		{"multiple-services", testdata.MultipleServicesDSL},
 		{"multiple-views", testdata.MultipleViewsDSL},
@@ -41,8 +43,10 @@ func TestFiles(t *testing.T) {
 		{"with-map", testdata.WithMapDSL},
 		{"path-with-wildcards", testdata.PathWithWildcardDSL},
 		{"with-tags", testdata.WithTagsDSL},
+		{"with-tags-swagger", testdata.WithTagsSwaggerDSL},
 		// TestEndpoints
 		{"endpoint", testdata.ExtensionDSL},
+		{"endpoint-swagger", testdata.ExtensionSwaggerDSL},
 		// TestValidations
 		{"string", testdata.StringValidationDSL},
 		{"integer", testdata.IntValidationDSL},
@@ -77,7 +81,7 @@ func TestFiles(t *testing.T) {
 					}
 					validateSwagger(t, buf.Bytes())
 
-					golden := filepath.Join(goldenPath, fmt.Sprintf("%s_%s.golden", c.Name, tname))
+					golden := filepath.Join(goldenPath, fmt.Sprintf("%s_%s.golden", strings.TrimSuffix(c.Name, "-swagger"), tname))
 					if *update {
 						if err := ioutil.WriteFile(golden, buf.Bytes(), 0644); err != nil {
 							t.Fatalf("failed to update golden file: %s", err)
diff --git a/http/codegen/testdata/openapi_dsls.go b/http/codegen/testdata/openapi_dsls.go
index 0cc71055..fbb82ea5 100644
--- a/http/codegen/testdata/openapi_dsls.go
+++ b/http/codegen/testdata/openapi_dsls.go
@@ -170,6 +170,15 @@ var EmptyDSL = func() {
 }
 
 var FileServiceDSL = func() {
+	var _ = Service("service-name", func() {
+		Files("path1", "filename")
+		Files("path2", "filename", func() {
+			Meta("openapi:tag:user-tag")
+		})
+	})
+}
+
+var FileServiceSwaggerDSL = func() {
 	var _ = Service("service-name", func() {
 		Files("path1", "filename")
 		Files("path2", "filename", func() {
@@ -276,6 +285,45 @@ var ArrayValidationDSL = func() {
 }
 
 var ExtensionDSL = func() {
+	var PayloadT = Type("Payload", func() {
+		Attribute("string", String, func() {
+			Example("")
+			Meta("openapi:extension:x-test-schema", "Payload")
+		})
+	})
+	var ResultT = Type("Result", func() {
+		Attribute("string", String, func() {
+			Example("")
+			Meta("openapi:extension:x-test-schema", "Result")
+		})
+	})
+	var _ = API("test", func() {
+		Server("test", func() {
+			Host("localhost", func() {
+				URI("https://goa.design")
+			})
+		})
+		Meta("openapi:extension:x-test-api", "API")
+		Meta("openapi:tag:Backend")
+		Meta("openapi:tag:Backend:desc", "Description of Backend")
+		Meta("openapi:tag:Backend:url", "http://example.com")
+		Meta("openapi:tag:Backend:url:desc", "See more docs here")
+		Meta("openapi:tag:Backend:extension:x-data", `{"foo":"bar"}`)
+	})
+	Service("testService", func() {
+		Method("testEndpoint", func() {
+			Payload(PayloadT)
+			Result(ResultT)
+			HTTP(func() {
+				POST("/")
+				Meta("openapi:extension:x-test-foo", "bar")
+			})
+			Meta("openapi:extension:x-test-operation", "Operation")
+		})
+	})
+}
+
+var ExtensionSwaggerDSL = func() {
 	var PayloadT = Type("Payload", func() {
 		Attribute("string", String, func() {
 			Example("")
@@ -486,6 +534,55 @@ var PathWithWildcardDSL = func() {
 }
 
 var WithTagsDSL = func() {
+	Service("test service", func() {
+		HTTP(func() {
+			Meta("openapi:tag:Service")
+			Meta("openapi:tag:Service:desc", "Service description")
+		})
+		Method("test endpoint", func() {
+			Payload(func() {
+				Attribute("int_map", Int)
+			})
+			HTTP(func() {
+				Meta("openapi:tag:Service")
+				Meta("openapi:tag:Service:desc", "Overwritten service description")
+				Meta("openapi:tag:Endpoint")
+				Meta("openapi:tag:Endpoint:desc", "Endpoint description")
+				Meta("openapi:tag:Endpoint:url", "Endpoint URL")
+				POST("/{*int_map}")
+			})
+		})
+		Method("another test endpoint", func() {
+			Payload(func() {
+				Attribute("int_map", Int)
+			})
+			HTTP(func() {
+				Meta("openapi:generate", "false")
+				Meta("openapi:tag:AnotherEndpoint")
+				Meta("openapi:tag:AnotherEndpoint:desc", "Endpoint description")
+				Meta("openapi:tag:AnotherEndpoint:url", "Endpoint URL")
+				POST("/{*int_map}")
+			})
+		})
+	})
+	Service("another test service", func() {
+		Meta("openapi:generate", "false")
+		HTTP(func() {
+			Meta("openapi:tag:AnotherService")
+			Meta("openapi:tag:AnotherService:desc", "Another service description")
+		})
+		Method("another test endpoint", func() {
+			Payload(func() {
+				Attribute("int_map", Int)
+			})
+			HTTP(func() {
+				POST("/{*int_map}")
+			})
+		})
+	})
+}
+
+var WithTagsSwaggerDSL = func() {
 	Service("test service", func() {
 		HTTP(func() {
 			Meta("swagger:tag:Service")