Repository: apigee/registry (go)

Issue:
Registry server: Allow nil bodies in create resource endpoints
Create resource endpoints currently allow empty string but not nil (e.g. [here](https://github.com/apigee/registry/blob/main/server/registry/actions_apis.go#L37)). The server should be permissive and allow both nil and empty string.

Pull request description:
allow nil bodies in create resource endpoints
Allow nil when creating:
* apis
* artifacts
* deployments
* projects
* specs
* versions

Fixes #1198

Patch:
diff --git a/server/registry/actions_apis.go b/server/registry/actions_apis.go
index 0a5448e..d468489 100644
--- a/server/registry/actions_apis.go
+++ b/server/registry/actions_apis.go
@@ -33,11 +33,6 @@ func (s *RegistryServer) CreateApi(ctx context.Context, req *rpc.CreateApiReques
 	if err != nil {
 		return nil, status.Error(codes.InvalidArgument, err.Error())
 	}
-	// API body must be nonempty.
-	if req.GetApi() == nil {
-		return nil, status.Errorf(codes.InvalidArgument, "invalid api %+v: body must be provided", req.GetApi())
-	}
-	// API name must be valid.
 	name := parent.Api(req.GetApiId())
 	if err := name.Validate(); err != nil {
 		return nil, status.Error(codes.InvalidArgument, err.Error())
diff --git a/server/registry/actions_artifacts.go b/server/registry/actions_artifacts.go
index 4982bc5..c16d3f1 100644
--- a/server/registry/actions_artifacts.go
+++ b/server/registry/actions_artifacts.go
@@ -60,10 +60,6 @@ func (s *RegistryServer) CreateArtifact(ctx context.Context, req *rpc.CreateArti
 	if err != nil {
 		return nil, status.Error(codes.InvalidArgument, err.Error())
 	}
-	// Artifact body must be nonempty.
-	if req.GetArtifact() == nil {
-		return nil, status.Errorf(codes.InvalidArgument, "invalid artifact %+v: body must be provided", req.GetArtifact())
-	}
 	var response *rpc.Artifact
 	if err := s.runInTransaction(ctx, func(ctx context.Context, db *storage.Client) error {
 		// Creation should only succeed when the parent exists.
diff --git a/server/registry/actions_deployments.go b/server/registry/actions_deployments.go
index b3fb13d..a058f8f 100644
--- a/server/registry/actions_deployments.go
+++ b/server/registry/actions_deployments.go
@@ -33,10 +33,6 @@ func (s *RegistryServer) CreateApiDeployment(ctx context.Context, req *rpc.Creat
 	if err != nil {
 		return nil, status.Error(codes.InvalidArgument, err.Error())
 	}
-	// Deployment body must be nonempty.
-	if req.GetApiDeployment() == nil {
-		return nil, status.Errorf(codes.InvalidArgument, "invalid api_deployment %+v: body must be provided", req.GetApiDeployment())
-	}
 	// Deployment name must be valid.
 	name := parent.Deployment(req.GetApiDeploymentId())
 	if err := name.Validate(); err != nil {
diff --git a/server/registry/actions_projects.go b/server/registry/actions_projects.go
index 4b22fab..360751a 100644
--- a/server/registry/actions_projects.go
+++ b/server/registry/actions_projects.go
@@ -28,10 +28,6 @@ import (
 
 // CreateProject handles the corresponding API request.
 func (s *RegistryServer) CreateProject(ctx context.Context, req *rpc.CreateProjectRequest) (*rpc.Project, error) {
-	// Project body must be nonempty.
-	if req.GetProject() == nil {
-		return nil, status.Errorf(codes.InvalidArgument, "invalid project %+v: body must be provided", req.GetProject())
-	}
 	// Project name must be valid.
 	name := names.Project{ProjectID: req.GetProjectId()}
 	if err := name.Validate(); err != nil {
diff --git a/server/registry/actions_specs.go b/server/registry/actions_specs.go
index 0a5bb74..a19c2a9 100644
--- a/server/registry/actions_specs.go
+++ b/server/registry/actions_specs.go
@@ -37,10 +37,6 @@ func (s *RegistryServer) CreateApiSpec(ctx context.Context, req *rpc.CreateApiSp
 	if err != nil {
 		return nil, status.Error(codes.InvalidArgument, err.Error())
 	}
-	// Spec body must be nonempty.
-	if req.GetApiSpec() == nil {
-		return nil, status.Errorf(codes.InvalidArgument, "invalid api_spec %+v: body must be provided", req.GetApiSpec())
-	}
 	// Spec name must be valid.
 	name := parent.Spec(req.GetApiSpecId())
 	if err := name.Validate(); err != nil {
diff --git a/server/registry/actions_versions.go b/server/registry/actions_versions.go
index cbfd747..9b0b72a 100644
--- a/server/registry/actions_versions.go
+++ b/server/registry/actions_versions.go
@@ -33,10 +33,6 @@ func (s *RegistryServer) CreateApiVersion(ctx context.Context, req *rpc.CreateAp
 	if err != nil {
 		return nil, status.Error(codes.InvalidArgument, err.Error())
 	}
-	// Version body must be nonempty.
-	if req.GetApiVersion() == nil {
-		return nil, status.Errorf(codes.InvalidArgument, "invalid api_version %+v: body must be provided", req.GetApiVersion())
-	}
 	// Version name must be valid.
 	name := parent.Version(req.GetApiVersionId())
 	if err := name.Validate(); err != nil {


Test patch:
diff --git a/server/registry/actions_apis_test.go b/server/registry/actions_apis_test.go
index 848bd29..0565273 100644
--- a/server/registry/actions_apis_test.go
+++ b/server/registry/actions_apis_test.go
@@ -74,6 +74,20 @@ func TestCreateApi(t *testing.T) {
 				},
 			},
 		},
+		{
+			desc: "empty resource",
+			seed: &rpc.Project{
+				Name: "projects/my-project",
+			},
+			req: &rpc.CreateApiRequest{
+				Parent: "projects/my-project/locations/global",
+				ApiId:  "my-api",
+				Api:    nil,
+			},
+			want: &rpc.Api{
+				Name: "projects/my-project/locations/global/apis/my-api",
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -150,7 +164,7 @@ func TestCreateApiResponseCodes(t *testing.T) {
 				ApiId:  "valid-id",
 				Api:    nil,
 			},
-			want: codes.InvalidArgument,
+			want: codes.OK,
 		},
 		{
 			desc: "missing custom identifier",
diff --git a/server/registry/actions_artifacts_test.go b/server/registry/actions_artifacts_test.go
index 1e672fe..31854bc 100644
--- a/server/registry/actions_artifacts_test.go
+++ b/server/registry/actions_artifacts_test.go
@@ -136,6 +136,18 @@ func TestCreateArtifact(t *testing.T) {
 				},
 			},
 		},
+		{
+			desc: "create empty artifact",
+			seed: &rpc.Project{Name: "projects/my-project"},
+			req: &rpc.CreateArtifactRequest{
+				Parent:     "projects/my-project/locations/global",
+				ArtifactId: "my-artifact",
+				Artifact:   nil,
+			},
+			want: &rpc.Artifact{
+				Name: "projects/my-project/locations/global/artifacts/my-artifact",
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -252,7 +264,7 @@ func TestCreateArtifactResponseCodes(t *testing.T) {
 				ArtifactId: "valid-id",
 				Artifact:   nil,
 			},
-			want: codes.InvalidArgument,
+			want: codes.OK,
 		},
 		{
 			desc: "missing custom identifier",
diff --git a/server/registry/actions_deployments_test.go b/server/registry/actions_deployments_test.go
index 1626eb8..a7577cf 100644
--- a/server/registry/actions_deployments_test.go
+++ b/server/registry/actions_deployments_test.go
@@ -64,6 +64,18 @@ func TestCreateApiDeployment(t *testing.T) {
 				},
 			},
 		},
+		{
+			desc: "empty resource",
+			seed: &rpc.Api{Name: "projects/my-project/locations/global/apis/a"},
+			req: &rpc.CreateApiDeploymentRequest{
+				Parent:          "projects/my-project/locations/global/apis/a",
+				ApiDeploymentId: "my-deployment",
+				ApiDeployment:   nil,
+			},
+			want: &rpc.ApiDeployment{
+				Name: "projects/my-project/locations/global/apis/a/deployments/my-deployment",
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -152,7 +164,7 @@ func TestCreateApiDeploymentResponseCodes(t *testing.T) {
 				ApiDeploymentId: "valid-id",
 				ApiDeployment:   nil,
 			},
-			want: codes.InvalidArgument,
+			want: codes.OK,
 		},
 		{
 			desc: "missing custom identifier",
diff --git a/server/registry/actions_projects_test.go b/server/registry/actions_projects_test.go
index 9e335c2..ecf61b3 100644
--- a/server/registry/actions_projects_test.go
+++ b/server/registry/actions_projects_test.go
@@ -54,6 +54,16 @@ func TestCreateProject(t *testing.T) {
 				Description: "My Description",
 			},
 		},
+		{
+			desc: "empty resource",
+			req: &rpc.CreateProjectRequest{
+				ProjectId: "my-project",
+				Project:   nil,
+			},
+			want: &rpc.Project{
+				Name: "projects/my-project",
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -115,7 +125,7 @@ func TestCreateProjectResponseCodes(t *testing.T) {
 				ProjectId: "valid-id",
 				Project:   nil,
 			},
-			want: codes.InvalidArgument,
+			want: codes.OK,
 		},
 		{
 			desc: "missing custom identifier",
diff --git a/server/registry/actions_specs_test.go b/server/registry/actions_specs_test.go
index dc88bf3..ef57bca 100644
--- a/server/registry/actions_specs_test.go
+++ b/server/registry/actions_specs_test.go
@@ -86,6 +86,18 @@ func TestCreateApiSpec(t *testing.T) {
 				},
 			},
 		},
+		{
+			desc: "empty resource",
+			seed: &rpc.ApiVersion{Name: "projects/my-project/locations/global/apis/my-api/versions/v1"},
+			req: &rpc.CreateApiSpecRequest{
+				Parent:    "projects/my-project/locations/global/apis/my-api/versions/v1",
+				ApiSpecId: "my-spec",
+				ApiSpec:   nil,
+			},
+			want: &rpc.ApiSpec{
+				Name: "projects/my-project/locations/global/apis/my-api/versions/v1/specs/my-spec",
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -166,7 +178,7 @@ func TestCreateApiSpecResponseCodes(t *testing.T) {
 				ApiSpecId: "valid-id",
 				ApiSpec:   nil,
 			},
-			want: codes.InvalidArgument,
+			want: codes.OK,
 		},
 		{
 			desc: "missing custom identifier",
diff --git a/server/registry/actions_versions_test.go b/server/registry/actions_versions_test.go
index 85fbf04..11521ec 100644
--- a/server/registry/actions_versions_test.go
+++ b/server/registry/actions_versions_test.go
@@ -72,6 +72,20 @@ func TestCreateApiVersion(t *testing.T) {
 				},
 			},
 		},
+		{
+			desc: "empty resource",
+			seed: &rpc.Api{
+				Name: "projects/my-project/locations/global/apis/my-api",
+			},
+			req: &rpc.CreateApiVersionRequest{
+				Parent:       "projects/my-project/locations/global/apis/my-api",
+				ApiVersionId: "v1",
+				ApiVersion:   nil,
+			},
+			want: &rpc.ApiVersion{
+				Name: "projects/my-project/locations/global/apis/my-api/versions/v1",
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -154,7 +168,7 @@ func TestCreateApiVersionResponseCodes(t *testing.T) {
 				ApiVersionId: "valid-id",
 				ApiVersion:   nil,
 			},
-			want: codes.InvalidArgument,
+			want: codes.OK,
 		},
 		{
 			desc: "missing custom identifier",