Creating Group API resource that has duplicate name of existing record throws 500 instead of 400

Summary

When creating a new group using the API with a duplicate name of an existing group, the data validation throws a 500 error message without any context instead of the expected behavior of 400 bad request with details about the field.

This is similar to #208329 (closed) so I suspect that this affects other API resources as well.

Steps to reproduce

  1. Use the Web UI to create a group in a self-managed GitLab instance. Take note of the group ID number. This is used for creating child groups underneath.
  2. Use Postman or another API client to create a new child group. This will be the initial group. API Docs
    • name: Postman Group 1
    • path: postman-group-1a
    • parent_id: {group_id_from_step_1}
  3. Use Postman to create another group. This will be the duplicate group with the same name but a different path so it would seemingly be unique from a path perspective.
    • name: Postman Group 1
    • path: postman-group-1b
    • parent_id: {group_id_from_step_1}
  4. See the 500 error message in the response.
  5. To see the expected behavior, perform similar steps above but use different names and the same path.
  6. Create a baseline group.
    • name: Postman Group 2a
    • path: postman-group-2
    • parent_id: {group_id_from_step_1}
  7. Create a duplicate group.
    • name: Postman Group 2b
    • path: postman-group-2
    • parent_id: {group_id_from_step_1}
  8. See the 400 error message with the response reason context about the existing value.

Reproduced steps are in screenshots below.

Example Project

N/A. See group creation steps in reproduce instructions.

What is the current bug behavior?

A 500 error is returned when a group with the same name exists.

500 Internal Server Error

What is the expected correct behavior?

A 400 error is returned with an explanation of what the problem is.

400 Bad Request
{
    "message": "Failed to save group {:name=>[\"has already been taken\"]}"
}

Relevant logs and/or screenshots

Reproduce Step 2 - Creating a Baseline Group

Screen_Shot_2020-10-22_at_2.22.15_PM

Reproduce Step 3 - Creating a Duplicate Group (same name, different path) - Bug Behavior

Screen_Shot_2020-10-22_at_2.22.32_PM

Reproduce Step 6 - Create a Baseline Group

Screen_Shot_2020-10-22_at_2.22.52_PM

Reproduce Step 7 - Creating a Duplicate Group (different name, same path) - Expected Behavior

Screen_Shot_2020-10-22_at_2.23.02_PM

GitLab v13.4.1 Self Managed
/var/log/gitlab/gitlab-rails/api_json.log

{
	"time":"2020-10-22T21:22:20.310Z",
	"severity":"INFO",
	"duration_s":0.21631,
	"db_duration_s":0.15557,
	"view_duration_s":0.06074,
	"status":500,
	"method":"POST",
	"path":"/api/v4/groups",
	"params":[
		{
			"key":"name",
			"value":"Postman Group 1"
		},
		{
			"key":"path",
			"value":"postman-group-1b"
		},
		{
			"key":"parent_id",
			"value":"7209"
		}
	],
	"host":"gitlab-core.us.gitlabdemo.cloud",
	"remote_ip":"73.97.219.97, 73.97.219.97",
	"ua":"PostmanRuntime/7.26.3",
	"route":"/api/:version/groups",
	"user_id":52,
	"username":"jeffersonmartin",
	"exception.class":"ActiveRecord::RecordNotUnique",
	"exception.message":"PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_namespaces_on_name_and_parent_id" DETAIL: Key (name, parent_id)=(Postman Group 1, 7209) already exists. ",
	"exception.backtrace":[
		"app/services/groups/create_service.rb:31:in `execute'",
		"ee/app/services/ee/groups/create_service.rb:10:in `execute'",
		"lib/api/groups.rb:59:in `create_group'",
		"ee/lib/ee/api/groups.rb:28:in `create_group'",
		"lib/api/groups.rb:168:in `block (2 levels) in '",
		"ee/lib/gitlab/middleware/ip_restrictor.rb:14:in `block in call'",
		"ee/lib/gitlab/ip_address_state.rb:10:in `with'",
		"ee/lib/gitlab/middleware/ip_restrictor.rb:13:in `call'",
		"lib/gitlab/metrics/elasticsearch_rack_middleware.rb:16:in `call'",
		"lib/gitlab/middleware/rails_queue_duration.rb:33:in `call'",
		"lib/gitlab/metrics/rack_middleware.rb:16:in `block in call'",
		"lib/gitlab/metrics/transaction.rb:61:in `run'",
		"lib/gitlab/metrics/rack_middleware.rb:16:in `call'",
		"lib/gitlab/request_profiler/middleware.rb:17:in `call'",
		"lib/gitlab/jira/middleware.rb:19:in `call'",
		"lib/gitlab/middleware/go.rb:20:in `call'",
		"lib/gitlab/etag_caching/middleware.rb:13:in `call'",
		"lib/gitlab/middleware/multipart.rb:217:in `call'",
		"lib/gitlab/middleware/read_only/controller.rb:51:in `call'",
		"lib/gitlab/middleware/read_only.rb:18:in `call'",
		"lib/gitlab/middleware/same_site_cookies.rb:27:in `call'",
		"lib/gitlab/middleware/basic_health_check.rb:25:in `call'",
		"lib/gitlab/middleware/handle_ip_spoof_attack_error.rb:25:in `call'",
		"lib/gitlab/middleware/request_context.rb:23:in `call'",
		"config/initializers/fix_local_cache_middleware.rb:9:in `call'",
		"lib/gitlab/metrics/requests_rack_middleware.rb:60:in `call'",
		"lib/gitlab/middleware/release_env.rb:12:in `call'"
	],
	"queue_duration_s":0.030396,
	"redis_calls":5,
	"redis_duration_s":0.001689,
	"redis_read_bytes":1047,
	"redis_write_bytes":266,
	"redis_cache_calls":5,
	"redis_cache_duration_s":0.001689,
	"redis_cache_read_bytes":1047,
	"redis_cache_write_bytes":266,
	"correlation_id":"npAB8fZmHha",
	"meta.user":"jeffersonmartin",
	"meta.caller_id":"/api/:version/groups"
}

Output of checks

Results of GitLab environment info

Expand for output related to GitLab environment info

System information
System:		Ubuntu 18.04
Proxy:		no
Current User:	git
Using RVM:	no
Ruby Version:	2.6.6p146
Gem Version:	2.7.10
Bundler Version:1.17.3
Rake Version:	12.3.3
Redis Version:	5.0.9
Git Version:	2.28.0
Sidekiq Version:5.2.9
Go Version:	unknown

GitLab information
Version:	13.4.1-ee
Revision:	4b9c8135cd9
Directory:	/opt/gitlab/embedded/service/gitlab-rails
DB Adapter:	PostgreSQL
DB Version:	11.9
URL:		https://gitlab-core.us.gitlabdemo.cloud
HTTP Clone URL:	https://gitlab-core.us.gitlabdemo.cloud/some-group/some-project.git
SSH Clone URL:	git@gitlab-core.us.gitlabdemo.cloud:some-group/some-project.git
Elasticsearch:	yes
Geo:		no
Using LDAP:	no
Using Omniauth:	yes
Omniauth Providers:

GitLab Shell
Version:	13.7.0
Repository storage paths:
- default: 	/var/opt/gitlab/git-data/repositories
GitLab Shell path:		/opt/gitlab/embedded/service/gitlab-shell
Git:		/opt/gitlab/embedded/bin/git

Results of GitLab application Check

Expand for output related to the GitLab application check

(For installations with omnibus-gitlab package run and paste the output of: sudo gitlab-rake gitlab:check SANITIZE=true)

(For installations from source run and paste the output of: sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)

(we will only investigate if the tests are passing)

Possible fixes