Repository: goadesign/goa (go)

Issue:
meta for externalDocs and deprecated?
I can see the Operations object has these fields, but I've been playing around with the DSL to try and set these fields but have not been able to get it to work.

How does one set them using Meta? Would be nice to have a good example

Pull request:
adds openapi deprecated support as a dsl.
added deprecated support as a dsl for both v2 and v3 on HTTP routes.

Fixes #3449 

Patch:
diff --git a/dsl/method.go b/dsl/method.go
index df000fca..3679b6b4 100644
--- a/dsl/method.go
+++ b/dsl/method.go
@@ -36,3 +36,25 @@ func Method(name string, fn func()) {
 	ep := &expr.MethodExpr{Name: name, Service: s, DSLFunc: fn}
 	s.Methods = append(s.Methods, ep)
 }
+
+// Deprecated marks HTTP routes as deprecated in the generated OpenAPI specifications.
+//
+// Deprecated must appear in a Method HTTP expression.
+// 
+// Deprecated takes no argument.
+// Example:
+//
+//    Method("add", func() {
+//        HTTP(func() {
+//            GET("/")
+//            Deprecated()
+//        })
+//    })
+func Deprecated() {
+	_, ok := eval.Current().(*expr.HTTPEndpointExpr)
+	if !ok {
+		eval.IncompatibleDSL()
+		return
+	}
+	Meta("openapi:deprecated", "true")
+}
\ No newline at end of file
diff --git a/http/codegen/openapi/v2/builder.go b/http/codegen/openapi/v2/builder.go
index d41ec134..28ce12db 100644
--- a/http/codegen/openapi/v2/builder.go
+++ b/http/codegen/openapi/v2/builder.go
@@ -639,7 +639,7 @@ func buildPathFromExpr(s *V2, root *expr.RootExpr, h *expr.HostExpr, route *expr
 			}
 			requirements[i] = requirement
 		}
-
+		_, deprecated := endpoint.MethodExpr.Meta.Last("openapi:deprecated")
 		operation := &Operation{
 			Tags:         tagNames,
 			Description:  description,
@@ -651,11 +651,11 @@ func buildPathFromExpr(s *V2, root *expr.RootExpr, h *expr.HostExpr, route *expr
 			Produces:     produces,
 			Responses:    responses,
 			Schemes:      schemes,
-			Deprecated:   false,
+			Deprecated:   deprecated,
 			Extensions:   openapi.ExtensionsFromExpr(endpoint.MethodExpr.Meta),
 			Security:     requirements,
 		}
-
+		
 		if key == "" {
 			key = "/"
 		}
diff --git a/http/codegen/openapi/v3/builder.go b/http/codegen/openapi/v3/builder.go
index d45468f4..8ad7c679 100644
--- a/http/codegen/openapi/v3/builder.go
+++ b/http/codegen/openapi/v3/builder.go
@@ -122,16 +122,15 @@ func buildPaths(h *expr.HTTPExpr, bodies map[string]map[string]*EndpointBodies,
 		if !openapi.MustGenerate(svc.Meta) || !openapi.MustGenerate(svc.ServiceExpr.Meta) {
 			continue
 		}
-
 		exts := openapi.ExtensionsFromExpr(svc.Meta)
 		sbod := bodies[svc.Name()]
 
 		// endpoints
 		for _, e := range svc.HTTPEndpoints {
+
 			if !openapi.MustGenerate(e.Meta) || !openapi.MustGenerate(e.MethodExpr.Meta) {
 				continue
 			}
-
 			for _, r := range e.Routes {
 				for _, key := range r.FullPaths() {
 					// Remove any wildcards that is defined in path as a workaround to
@@ -339,7 +338,9 @@ func buildOperation(key string, r *expr.RouteExpr, bodies *EndpointBodies, rand
 			break
 		}
 	}
-
+	
+	// An endpoint may be marked as deprecated. if the openapi:deprecated tag is present, we populate it to true
+	_, deprecated := e.Meta.Last("openapi:deprecated")
 	return &Operation{
 		Tags:         tagNames,
 		Summary:      summary,
@@ -349,7 +350,7 @@ func buildOperation(key string, r *expr.RouteExpr, bodies *EndpointBodies, rand
 		RequestBody:  requestBody,
 		Responses:    responses,
 		Security:     buildSecurityRequirements(e.Requirements),
-		Deprecated:   false,
+		Deprecated:   deprecated,
 		ExternalDocs: openapi.DocsFromExpr(m.Docs, m.Meta),
 		Extensions:   openapi.ExtensionsFromExpr(m.Meta),
 	}
@@ -549,7 +550,7 @@ func buildSecurityRequirements(reqs []*expr.SecurityExpr) []map[string][]string
 	for i, req := range reqs {
 		sr := make(map[string][]string, len(req.Schemes))
 		for _, sch := range req.Schemes {
-			scopes := []string{}
+			scopes := make([]string, 0) 
 			switch sch.Kind {
 			case expr.OAuth2Kind, expr.JWTKind:
 				if len(req.Scopes) > 0 {


Test patch:
diff --git a/http/codegen/openapi/v2/builder_test.go b/http/codegen/openapi/v2/builder_test.go
index b162dc8b..f33b1b99 100644
--- a/http/codegen/openapi/v2/builder_test.go
+++ b/http/codegen/openapi/v2/builder_test.go
@@ -55,18 +55,23 @@ func TestBuildPathFromFileServer(t *testing.T) {
 func TestBuildPathFromExpr(t *testing.T) {
 	cases := map[string]struct {
 		multipartRequest bool
+		deprecated bool
 		expected         Operation
 	}{
 		"multipart request": {
 			multipartRequest: true,
+			deprecated: false,
 			expected: Operation{
+				Deprecated: false,
 				Consumes:   []string{"multipart/form-data"},
 				Parameters: []*Parameter{{In: "formData"}},
 			},
 		},
 		"non multipart request": {
 			multipartRequest: false,
+			deprecated: true,
 			expected: Operation{
+				Deprecated: true,
 				Consumes:   nil,
 				Parameters: []*Parameter{{In: "body"}},
 			},
@@ -103,8 +108,14 @@ func TestBuildPathFromExpr(t *testing.T) {
 						Type: expr.String,
 					},
 					MultipartRequest: tc.multipartRequest,
+					Meta: expr.MetaExpr{},
 				},
 			}
+			
+			if tc.deprecated {
+				route.Endpoint.Meta["openapi:deprecated"] = []string{"true"}
+			}
+
 			basePath := "/"
 			buildPathFromExpr(s, root, h, route, basePath)
 			for _, path := range s.Paths {
diff --git a/http/codegen/openapi/v3/builder_test.go b/http/codegen/openapi/v3/builder_test.go
index 00696d1c..3e98f658 100644
--- a/http/codegen/openapi/v3/builder_test.go
+++ b/http/codegen/openapi/v3/builder_test.go
@@ -118,6 +118,7 @@ func TestBuildOperation(t *testing.T) {
 		DSL  func()
 
 		ExpectedDescription string
+		ExpectedDeprecated bool
 		ExpectedParameters  []param
 		ExpectedRequestBody *requestBody
 		ExpectedResponses   map[string]response
@@ -126,50 +127,64 @@ func TestBuildOperation(t *testing.T) {
 		DSL:  dsls.DescOnly(svcName, "desc_only", "desc"),
 
 		ExpectedDescription: "desc",
+		ExpectedDeprecated: false,
+		ExpectedResponses:   responses{"204": {Description: "No Content response."}},
+	}, {
+		Name: "deprecated_only",
+		DSL: dsls.DeprecatedOnly(svcName, "deprecated_only"),
+		ExpectedDeprecated: true,
 		ExpectedResponses:   responses{"204": {Description: "No Content response."}},
 	}, {
 		Name: "request_string_body",
 		DSL:  dsls.RequestStringBody(svcName, "request_string_body"),
 
+		ExpectedDeprecated: false,
 		ExpectedRequestBody: &requestBody{"body", tstring, true},
 		ExpectedResponses:   responses{"204": {Description: "No Content response."}},
 	}, {
 		Name: "request_object_body",
 		DSL:  dsls.RequestObjectBody(svcName, "request_object_body"),
 
+		ExpectedDeprecated: false,
 		ExpectedRequestBody: &requestBody{"", tobj("name", tstring), true},
 		ExpectedResponses:   responses{"204": {Description: "No Content response."}},
 	}, {
 		Name: "request_streaming_string_body",
 		DSL:  dsls.RequestObjectBody(svcName, "request_streaming_string_body"),
 
+		ExpectedDeprecated: false,
 		ExpectedRequestBody: &requestBody{"", tobj("name", tstring), true},
 		ExpectedResponses:   responses{"204": {Description: "No Content response."}},
 	}, {
 		Name: "request_map_params",
 		DSL:  dsls.RequestMapParams(svcName, "request_map_params"),
 
+		ExpectedDeprecated: false,
 		ExpectedParameters: []param{{Name: "param", In: "query", Description: "Query parameters", Style: "deepObject", Type: tobj()}},
 		ExpectedResponses:  responses{"204": {Description: "No Content response."}},
 	}, {
 		Name: "response_array_of_string",
 		DSL:  dsls.ResponseArrayOfString(svcName, "response_array_of_string"),
-
+		
+		ExpectedDeprecated: false,
 		ExpectedResponses: responses{"200": {"OK response.", tobj("result", tobj("children", tarray)), nil}},
 	}, {
 		Name: "response_recursive_user_type",
 		DSL:  dsls.ResponseRecursiveUserType(svcName, "response_recursive_user_type"),
 
+		ExpectedDeprecated: false,
 		ExpectedResponses: responses{"200": {"OK response.", tobj("recursive", tobj()), nil}},
 	}, {
 		Name: "response_recursive_array_user_type",
 		DSL:  dsls.ResponseRecursiveArrayUserType(svcName, "response_recursive_array_user_type"),
 
+		ExpectedDeprecated: false,
 		ExpectedResponses: responses{"200": {"OK response.", tobj("result", tobj("children", tarray)), nil}},
 	}, {
 		Name: "response_skip_response_body_encode_decode",
 		DSL:  dsls.ResponseSkipResponseBodyEncodeDecode(svcName, "response_skip_response_body_encode_decode"),
 
+		ExpectedDeprecated: false,
 		ExpectedResponses: responses{"200": {"OK response.", tbinary, nil}},
 	}}
 	for _, c := range cases {
@@ -223,6 +238,11 @@ func TestBuildOperation(t *testing.T) {
 				t.Errorf("got %d parameters, expected %d", len(op.Parameters), len(c.ExpectedParameters))
 				return
 			}
+
+			if op.Deprecated != c.ExpectedDeprecated {
+				t.Errorf("got %t deprecated, expected %t", op.Deprecated, c.ExpectedDeprecated)
+			}
+
 			for i, p := range op.Parameters {
 				matchesParameter(t, p, types, c.ExpectedParameters[i])
 			}
diff --git a/http/codegen/openapi/v3/testdata/dsls/operations.go b/http/codegen/openapi/v3/testdata/dsls/operations.go
index dfeae3d4..9601944c 100644
--- a/http/codegen/openapi/v3/testdata/dsls/operations.go
+++ b/http/codegen/openapi/v3/testdata/dsls/operations.go
@@ -15,6 +15,19 @@ var DescOnly = func(svc, met, desc string) func() {
 	}
 }
 
+var DeprecatedOnly = func(svc, met string) func() {
+	return func() {
+		var _ = Service(svc, func() {
+			Method(met, func() {
+				HTTP(func() {
+					GET("/")
+					Deprecated()
+				})
+			})
+		})
+	}
+}
+
 var RequestStringBody = func(svc, met string) func() {
 	return func() {
 		var _ = Service(svc, func() {