Add `details` to error response for `POST /api/v1/accounts` in REST API (#15803)
parent
b4cb8c3c83
commit
9aa37b32c3
|
@ -27,6 +27,8 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
|
|
||||||
self.response_body = Oj.dump(response.body)
|
self.response_body = Oj.dump(response.body)
|
||||||
self.status = response.status
|
self.status = response.status
|
||||||
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
|
render json: ValidationErrorFormatter.new(e, :'account.username' => :username, :'invite_request.text' => :reason).as_json, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ValidationErrorFormatter
|
||||||
|
def initialize(error, aliases = {})
|
||||||
|
@error = error
|
||||||
|
@aliases = aliases
|
||||||
|
end
|
||||||
|
|
||||||
|
def as_json
|
||||||
|
{ error: @error.to_s, details: details }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def details
|
||||||
|
h = {}
|
||||||
|
|
||||||
|
errors.details.each_pair do |attribute_name, attribute_errors|
|
||||||
|
messages = errors.messages[attribute_name]
|
||||||
|
|
||||||
|
h[@aliases[attribute_name] || attribute_name] = attribute_errors.map.with_index do |error, index|
|
||||||
|
{ error: 'ERR_' + error[:error].to_s.upcase, description: messages[index] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
h
|
||||||
|
end
|
||||||
|
|
||||||
|
def errors
|
||||||
|
@errors ||= @error.record.errors
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
class BlacklistedEmailValidator < ActiveModel::Validator
|
class BlacklistedEmailValidator < ActiveModel::Validator
|
||||||
def validate(user)
|
def validate(user)
|
||||||
return if user.valid_invitation?
|
return if user.valid_invitation? || user.email.blank?
|
||||||
|
|
||||||
@email = user.email
|
@email = user.email
|
||||||
|
|
||||||
user.errors.add(:email, I18n.t('users.blocked_email_provider')) if blocked_email?
|
user.errors.add(:email, :blocked) if blocked_email?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -4,16 +4,19 @@ require 'resolv'
|
||||||
|
|
||||||
class EmailMxValidator < ActiveModel::Validator
|
class EmailMxValidator < ActiveModel::Validator
|
||||||
def validate(user)
|
def validate(user)
|
||||||
|
return if user.email.blank?
|
||||||
|
|
||||||
domain = get_domain(user.email)
|
domain = get_domain(user.email)
|
||||||
|
|
||||||
if domain.nil?
|
if domain.blank?
|
||||||
user.errors.add(:email, I18n.t('users.invalid_email'))
|
user.errors.add(:email, :invalid)
|
||||||
else
|
else
|
||||||
ips, hostnames = resolve_mx(domain)
|
ips, hostnames = resolve_mx(domain)
|
||||||
|
|
||||||
if ips.empty?
|
if ips.empty?
|
||||||
user.errors.add(:email, I18n.t('users.invalid_email_mx'))
|
user.errors.add(:email, :unreachable)
|
||||||
elsif on_blacklist?(hostnames + ips)
|
elsif on_blacklist?(hostnames + ips)
|
||||||
user.errors.add(:email, I18n.t('users.blocked_email_provider'))
|
user.errors.add(:email, :blocked)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
class NoteLengthValidator < ActiveModel::EachValidator
|
class NoteLengthValidator < ActiveModel::EachValidator
|
||||||
def validate_each(record, attribute, value)
|
def validate_each(record, attribute, value)
|
||||||
record.errors.add(attribute, I18n.t('statuses.over_character_limit', max: options[:maximum])) if too_long?(value)
|
record.errors.add(attribute, :too_long, message: I18n.t('statuses.over_character_limit', max: options[:maximum]), count: options[:maximum]) if too_long?(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
class UniqueUsernameValidator < ActiveModel::Validator
|
class UniqueUsernameValidator < ActiveModel::Validator
|
||||||
def validate(account)
|
def validate(account)
|
||||||
return if account.username.nil?
|
return if account.username.blank?
|
||||||
|
|
||||||
normalized_username = account.username.downcase
|
normalized_username = account.username.downcase
|
||||||
normalized_domain = account.domain&.downcase
|
normalized_domain = account.domain&.downcase
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
class UnreservedUsernameValidator < ActiveModel::Validator
|
class UnreservedUsernameValidator < ActiveModel::Validator
|
||||||
def validate(account)
|
def validate(account)
|
||||||
@username = account.username
|
@username = account.username
|
||||||
return if @username.nil?
|
|
||||||
|
|
||||||
account.errors.add(:username, I18n.t('accounts.reserved_username')) if reserved_username?
|
return if @username.blank?
|
||||||
|
|
||||||
|
account.errors.add(:username, :reserved) if reserved_username?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -5,13 +5,28 @@ en:
|
||||||
poll:
|
poll:
|
||||||
expires_at: Deadline
|
expires_at: Deadline
|
||||||
options: Choices
|
options: Choices
|
||||||
|
user:
|
||||||
|
agreement: Service agreement
|
||||||
|
email: E-mail address
|
||||||
|
locale: Locale
|
||||||
|
password: Password
|
||||||
|
user/account:
|
||||||
|
username: Username
|
||||||
|
user/invite_request:
|
||||||
|
text: Reason
|
||||||
errors:
|
errors:
|
||||||
models:
|
models:
|
||||||
account:
|
account:
|
||||||
attributes:
|
attributes:
|
||||||
username:
|
username:
|
||||||
invalid: only letters, numbers and underscores
|
invalid: must contain only letters, numbers and underscores
|
||||||
|
reserved: is reserved
|
||||||
status:
|
status:
|
||||||
attributes:
|
attributes:
|
||||||
reblog:
|
reblog:
|
||||||
taken: of status already exists
|
taken: of status already exists
|
||||||
|
user:
|
||||||
|
attributes:
|
||||||
|
email:
|
||||||
|
blocked: uses a disallowed e-mail provider
|
||||||
|
unreachable: does not seem to exist
|
||||||
|
|
|
@ -80,7 +80,6 @@ en:
|
||||||
other: Toots
|
other: Toots
|
||||||
posts_tab_heading: Toots
|
posts_tab_heading: Toots
|
||||||
posts_with_replies: Toots and replies
|
posts_with_replies: Toots and replies
|
||||||
reserved_username: The username is reserved
|
|
||||||
roles:
|
roles:
|
||||||
admin: Admin
|
admin: Admin
|
||||||
bot: Bot
|
bot: Bot
|
||||||
|
@ -1410,11 +1409,8 @@ en:
|
||||||
tips: Tips
|
tips: Tips
|
||||||
title: Welcome aboard, %{name}!
|
title: Welcome aboard, %{name}!
|
||||||
users:
|
users:
|
||||||
blocked_email_provider: This e-mail provider isn't allowed
|
|
||||||
follow_limit_reached: You cannot follow more than %{limit} people
|
follow_limit_reached: You cannot follow more than %{limit} people
|
||||||
generic_access_help_html: Trouble accessing your account? You may get in touch with %{email} for assistance
|
generic_access_help_html: Trouble accessing your account? You may get in touch with %{email} for assistance
|
||||||
invalid_email: The e-mail address is invalid
|
|
||||||
invalid_email_mx: The e-mail address does not seem to exist
|
|
||||||
invalid_otp_token: Invalid two-factor code
|
invalid_otp_token: Invalid two-factor code
|
||||||
invalid_sign_in_token: Invalid security code
|
invalid_sign_in_token: Invalid security code
|
||||||
otp_lost_help_html: If you lost access to both, you may get in touch with %{email}
|
otp_lost_help_html: If you lost access to both, you may get in touch with %{email}
|
||||||
|
|
|
@ -69,7 +69,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows a login error' do
|
it 'shows a login error' do
|
||||||
expect(flash[:alert]).to match I18n.t('devise.failure.invalid', authentication_keys: 'Email')
|
expect(flash[:alert]).to match I18n.t('devise.failure.invalid', authentication_keys: I18n.t('activerecord.attributes.user.email'))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't log the user in" do
|
it "doesn't log the user in" do
|
||||||
|
@ -136,7 +136,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows a login error' do
|
it 'shows a login error' do
|
||||||
expect(flash[:alert]).to match I18n.t('devise.failure.invalid', authentication_keys: 'Email')
|
expect(flash[:alert]).to match I18n.t('devise.failure.invalid', authentication_keys: I18n.t('activerecord.attributes.user.email'))
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't log the user in" do
|
it "doesn't log the user in" do
|
||||||
|
|
|
@ -17,7 +17,7 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do
|
||||||
let(:blocked_email) { true }
|
let(:blocked_email) { true }
|
||||||
|
|
||||||
it 'calls errors.add' do
|
it 'calls errors.add' do
|
||||||
expect(errors).to have_received(:add).with(:email, I18n.t('users.blocked_email_provider'))
|
expect(errors).to have_received(:add).with(:email, :blocked)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do
|
||||||
let(:blocked_email) { false }
|
let(:blocked_email) { false }
|
||||||
|
|
||||||
it 'not calls errors.add' do
|
it 'not calls errors.add' do
|
||||||
expect(errors).not_to have_received(:add).with(:email, I18n.t('users.blocked_email_provider'))
|
expect(errors).not_to have_received(:add).with(:email, :blocked)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ RSpec.describe UnreservedUsernameValidator, type: :validator do
|
||||||
let(:account) { double(username: username, errors: errors) }
|
let(:account) { double(username: username, errors: errors) }
|
||||||
let(:errors ) { double(add: nil) }
|
let(:errors ) { double(add: nil) }
|
||||||
|
|
||||||
context '@username.nil?' do
|
context '@username.blank?' do
|
||||||
let(:username) { nil }
|
let(:username) { nil }
|
||||||
|
|
||||||
it 'not calls errors.add' do
|
it 'not calls errors.add' do
|
||||||
|
@ -21,14 +21,14 @@ RSpec.describe UnreservedUsernameValidator, type: :validator do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context '!@username.nil?' do
|
context '!@username.blank?' do
|
||||||
let(:username) { '' }
|
let(:username) { 'f' }
|
||||||
|
|
||||||
context 'reserved_username?' do
|
context 'reserved_username?' do
|
||||||
let(:reserved_username) { true }
|
let(:reserved_username) { true }
|
||||||
|
|
||||||
it 'calls erros.add' do
|
it 'calls erros.add' do
|
||||||
expect(errors).to have_received(:add).with(:username, I18n.t('accounts.reserved_username'))
|
expect(errors).to have_received(:add).with(:username, :reserved)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Reference in New Issue