Commit 29c26e46 authored by Markus Koller's avatar Markus Koller

Return empty body for 204 responses in API

Previously these would just return the `.to_s` representation of the
Grape route block expression, e.g. `"<Project:0x0000560fe150ce80>`.

This is now changed to always use the `no_content!` helper. Since this
aborts the execution we also have to tweak the order slightly,
especially with the `destroy_conditionally!` helper.
parent 570c89dc
---
title: Return empty body for 204 responses in API
merge_request: 22086
author:
type: fixed
...@@ -92,6 +92,12 @@ For instance: ...@@ -92,6 +92,12 @@ For instance:
Model.create(foo: params[:foo]) Model.create(foo: params[:foo])
``` ```
## Using HTTP status helpers
For non-200 HTTP responses, use the provided helpers in `lib/api/helpers.rb` to ensure correct behaviour (`not_found!`, `no_content!` etc.). These will `throw` inside Grape and abort the execution of your endpoint.
For `DELETE` requests, you should also generally use the `destroy_conditionally!` helper which by default returns a `204 No Content` response on success, or a `412 Precondition Failed` response if the given `If-Unmodified-Since` header is out of range. This helper calls `#destroy` on the passed resource, but you can also implement a custom deletion method by passing a block.
## Using API path helpers in GitLab Rails codebase ## Using API path helpers in GitLab Rails codebase
Because we support [installing GitLab under a relative URL], one must take this Because we support [installing GitLab under a relative URL], one must take this
......
...@@ -203,7 +203,8 @@ module API ...@@ -203,7 +203,8 @@ module API
not_found!('GeoNode') unless geo_node not_found!('GeoNode') unless geo_node
geo_node.destroy! geo_node.destroy!
status 204
no_content!
end end
end end
end end
......
...@@ -41,7 +41,7 @@ module API ...@@ -41,7 +41,7 @@ module API
ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn]) ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn])
if ldap_group_link if ldap_group_link
ldap_group_link.destroy ldap_group_link.destroy
status 204 no_content!
else else
render_api_error!('Linked LDAP group not found', 404) render_api_error!('Linked LDAP group not found', 404)
end end
...@@ -61,7 +61,7 @@ module API ...@@ -61,7 +61,7 @@ module API
ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn], provider: params[:provider]) ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn], provider: params[:provider])
if ldap_group_link if ldap_group_link
ldap_group_link.destroy ldap_group_link.destroy
status 204 no_content!
else else
render_api_error!('Linked LDAP group not found', 404) render_api_error!('Linked LDAP group not found', 404)
end end
......
...@@ -113,8 +113,9 @@ module API ...@@ -113,8 +113,9 @@ module API
authorize_can_admin! authorize_can_admin!
not_found!('SoftwareLicensePolicy') unless software_license_policy not_found!('SoftwareLicensePolicy') unless software_license_policy
status 204
software_license_policy.destroy! software_license_policy.destroy!
no_content!
end end
end end
end end
......
...@@ -58,7 +58,7 @@ module API ...@@ -58,7 +58,7 @@ module API
delete ':name' do delete ':name' do
project_alias.destroy project_alias.destroy
status 204 no_content!
end end
end end
end end
......
...@@ -78,7 +78,8 @@ module API ...@@ -78,7 +78,8 @@ module API
not_found!('Push Rule') unless push_rule not_found!('Push Rule') unless push_rule
push_rule.destroy push_rule.destroy
status 204
no_content!
end end
end end
end end
......
...@@ -157,9 +157,7 @@ module API ...@@ -157,9 +157,7 @@ module API
updated = update_scim_user(identity) updated = update_scim_user(identity)
if updated if updated
status 204 no_content!
{}
else else
scim_error!(message: "Error updating #{identity.user.name} with #{params.inspect}") scim_error!(message: "Error updating #{identity.user.name} with #{params.inspect}")
end end
...@@ -180,9 +178,7 @@ module API ...@@ -180,9 +178,7 @@ module API
scim_not_found!(message: "Resource #{params[:id]} not found") unless destroyed scim_not_found!(message: "Resource #{params[:id]} not found") unless destroyed
status 204 no_content!
{}
end end
end end
end end
......
...@@ -249,7 +249,7 @@ describe API::Scim do ...@@ -249,7 +249,7 @@ describe API::Scim do
end end
it 'responds with an empty response' do it 'responds with an empty response' do
expect(json_response).to eq({}) expect(response.body).to eq('')
end end
end end
...@@ -322,7 +322,7 @@ describe API::Scim do ...@@ -322,7 +322,7 @@ describe API::Scim do
end end
it 'responds with an empty response' do it 'responds with an empty response' do
expect(json_response).to eq({}) expect(response.body).to eq('')
end end
end end
......
...@@ -38,7 +38,7 @@ module API ...@@ -38,7 +38,7 @@ module API
application = ApplicationsFinder.new(params).execute application = ApplicationsFinder.new(params).execute
application.destroy application.destroy
status 204 no_content!
end end
end end
end end
......
...@@ -135,7 +135,6 @@ module API ...@@ -135,7 +135,6 @@ module API
end end
destroy_conditionally!(badge) destroy_conditionally!(badge)
body false
end end
end end
end end
......
...@@ -57,7 +57,7 @@ module API ...@@ -57,7 +57,7 @@ module API
requires :branch, type: String, desc: 'The name of the branch' requires :branch, type: String, desc: 'The name of the branch'
end end
head do head do
user_project.repository.branch_exists?(params[:branch]) ? status(204) : status(404) user_project.repository.branch_exists?(params[:branch]) ? no_content! : not_found!
end end
get do get do
branch = find_branch!(params[:branch]) branch = find_branch!(params[:branch])
......
...@@ -77,7 +77,7 @@ module API ...@@ -77,7 +77,7 @@ module API
resource.custom_attributes.find_by!(key: params[:key]).destroy resource.custom_attributes.find_by!(key: params[:key]).destroy
status 204 no_content!
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
......
...@@ -74,7 +74,7 @@ module API ...@@ -74,7 +74,7 @@ module API
delete ':name' do delete ':name' do
Feature.get(params[:name]).remove Feature.get(params[:name]).remove
status 204 no_content!
end end
end end
end end
......
...@@ -67,7 +67,7 @@ module API ...@@ -67,7 +67,7 @@ module API
milestone = user_group.milestones.find(params[:milestone_id]) milestone = user_group.milestones.find(params[:milestone_id])
Milestones::DestroyService.new(user_group, current_user).execute(milestone) Milestones::DestroyService.new(user_group, current_user).execute(milestone)
status(204) no_content!
end end
desc 'Get all issues for a single group milestone' do desc 'Get all issues for a single group milestone' do
......
...@@ -31,6 +31,7 @@ module API ...@@ -31,6 +31,7 @@ module API
check_unmodified_since!(last_updated) check_unmodified_since!(last_updated)
status 204 status 204
body false
if block_given? if block_given?
yield resource yield resource
......
...@@ -17,9 +17,9 @@ module API ...@@ -17,9 +17,9 @@ module API
delete ':id/pages' do delete ':id/pages' do
authorize! :remove_pages, user_project authorize! :remove_pages, user_project
status 204
::Pages::DeleteService.new(user_project, current_user).execute ::Pages::DeleteService.new(user_project, current_user).execute
no_content!
end end
end end
end end
......
...@@ -148,8 +148,9 @@ module API ...@@ -148,8 +148,9 @@ module API
delete ":id/pages/domains/:domain", requirements: PAGES_DOMAINS_ENDPOINT_REQUIREMENTS do delete ":id/pages/domains/:domain", requirements: PAGES_DOMAINS_ENDPOINT_REQUIREMENTS do
authorize! :update_pages, user_project authorize! :update_pages, user_project
status 204
pages_domain.destroy pages_domain.destroy
no_content!
end end
end end
end end
......
...@@ -69,7 +69,7 @@ module API ...@@ -69,7 +69,7 @@ module API
milestone = user_project.milestones.find(params[:milestone_id]) milestone = user_project.milestones.find(params[:milestone_id])
Milestones::DestroyService.new(user_project, current_user).execute(milestone) Milestones::DestroyService.new(user_project, current_user).execute(milestone)
status(204) no_content!
end end
desc 'Get all issues for a single project milestone' do desc 'Get all issues for a single project milestone' do
......
...@@ -447,7 +447,7 @@ module API ...@@ -447,7 +447,7 @@ module API
::Projects::UnlinkForkService.new(user_project, current_user).execute ::Projects::UnlinkForkService.new(user_project, current_user).execute
end end
result ? status(204) : not_modified! not_modified! unless result
end end
desc 'Share the project with a group' do desc 'Share the project with a group' do
......
...@@ -346,8 +346,9 @@ module API ...@@ -346,8 +346,9 @@ module API
key = user.gpg_keys.find_by(id: params[:key_id]) key = user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key not_found!('GPG Key') unless key
status 204
key.destroy key.destroy
no_content!
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -760,8 +761,9 @@ module API ...@@ -760,8 +761,9 @@ module API
key = current_user.gpg_keys.find_by(id: params[:key_id]) key = current_user.gpg_keys.find_by(id: params[:key_id])
not_found!('GPG Key') unless key not_found!('GPG Key') unless key
status 204
key.destroy key.destroy
no_content!
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -111,9 +111,10 @@ module API ...@@ -111,9 +111,10 @@ module API
variable = user_project.variables.find_by(key: params[:key]) variable = user_project.variables.find_by(key: params[:key])
not_found!('Variable') unless variable not_found!('Variable') unless variable
# Variables don't have any timestamp. Therfore, destroy unconditionally. # Variables don't have a timestamp. Therefore, destroy unconditionally.
status 204
variable.destroy variable.destroy
no_content!
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
......
...@@ -107,8 +107,9 @@ module API ...@@ -107,8 +107,9 @@ module API
delete ':id/wikis/:slug' do delete ':id/wikis/:slug' do
authorize! :admin_wiki, user_project authorize! :admin_wiki, user_project
status 204
WikiPages::DestroyService.new(user_project, current_user).execute(wiki_page) WikiPages::DestroyService.new(user_project, current_user).execute(wiki_page)
no_content!
end end
desc 'Upload an attachment to the wiki repository' do desc 'Upload an attachment to the wiki repository' do
......
...@@ -59,8 +59,9 @@ shared_examples_for '412 response' do ...@@ -59,8 +59,9 @@ shared_examples_for '412 response' do
delete request, params: params, headers: { 'HTTP_IF_UNMODIFIED_SINCE' => '1990-01-12T00:00:48-0600' } delete request, params: params, headers: { 'HTTP_IF_UNMODIFIED_SINCE' => '1990-01-12T00:00:48-0600' }
end end
it 'returns 412' do it 'returns 412 with a JSON error' do
expect(response).to have_gitlab_http_status(412) expect(response).to have_gitlab_http_status(412)
expect(json_response).to eq('message' => '412 Precondition Failed')
end end
end end
...@@ -69,8 +70,9 @@ shared_examples_for '412 response' do ...@@ -69,8 +70,9 @@ shared_examples_for '412 response' do
delete request, params: params, headers: { 'HTTP_IF_UNMODIFIED_SINCE' => Time.now } delete request, params: params, headers: { 'HTTP_IF_UNMODIFIED_SINCE' => Time.now }
end end
it 'returns accepted' do it 'returns 204 with an empty body' do
expect(response).to have_gitlab_http_status(success_status) expect(response).to have_gitlab_http_status(success_status)
expect(response.body).to eq('') if success_status == 204
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment