From 78e33538d1c252d0e72015b5a2935889f2ea2c84 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 19 Aug 2026 19:07:07 +0000 Subject: [PATCH] Change empty search requests to return a HTTP 400 error (#40214) --- app/controllers/api/v2/search_controller.rb | 3 +-- app/controllers/concerns/api/error_handling.rb | 2 +- app/lib/search_query_transformer.rb | 11 +++++++---- lib/exceptions.rb | 1 - spec/lib/search_query_transformer_spec.rb | 10 +++++----- spec/requests/api/v2/search_spec.rb | 6 +++--- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb index c00ddf92cc0..c2fcc644390 100644 --- a/app/controllers/api/v2/search_controller.rb +++ b/app/controllers/api/v2/search_controller.rb @@ -13,14 +13,13 @@ class Api::V2::SearchController < Api::BaseController before_action :query_pagination_error, if: :pagination_requested? before_action :remote_resolve_error, if: :remote_resolve_requested? end + before_action :require_valid_pagination_options! before_action :handle_fasp_requests def index @search = Search.new(search_results) render json: @search, serializer: REST::SearchSerializer - rescue Mastodon::SyntaxError - unprocessable_content rescue ActiveRecord::RecordNotFound not_found end diff --git a/app/controllers/concerns/api/error_handling.rb b/app/controllers/concerns/api/error_handling.rb index 9ce4795b02b..8f2a7e6fd4e 100644 --- a/app/controllers/concerns/api/error_handling.rb +++ b/app/controllers/concerns/api/error_handling.rb @@ -45,7 +45,7 @@ module Api::ErrorHandling render json: { error: I18n.t('errors.429') }, status: 429 end - rescue_from ActionController::ParameterMissing, Mastodon::InvalidParameterError do |e| + rescue_from ActionController::ParameterMissing, Mastodon::InvalidParameterError, SearchQueryTransformer::QueryError do |e| render json: { error: e.to_s }, status: 400 end end diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index b484c0fcea1..7f5be4c74fc 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -12,6 +12,9 @@ class SearchQueryTransformer < Parslet::Transform in ).freeze + class TransformerError < StandardError; end + class QueryError < StandardError; end + class Query def initialize(clauses, options = {}) raise ArgumentError if options[:current_account].nil? @@ -37,7 +40,7 @@ class SearchQueryTransformer < Parslet::Transform def validate_clauses! # At least one clause should be a positive match unless searching within the library - raise 'Empty query not supported' if @flags['in'] != 'library' && (must_clauses + filter_clauses).none? { |clause| clause.is_a?(TermClause) && clause.term.present? } + raise QueryError, 'At least one keyword or phrase is required' if @flags['in'] != 'library' && (must_clauses + filter_clauses).none? { |clause| clause.is_a?(TermClause) && clause.term.present? } end def clauses_by_operator @@ -113,7 +116,7 @@ class SearchQueryTransformer < Parslet::Transform when '-' :must_not else - raise "Unknown operator: #{str}" + raise TransformerError, "Unknown operator: #{str}" end end end @@ -182,7 +185,7 @@ class SearchQueryTransformer < Parslet::Transform @operator = :flag @term = term else - raise "Unknown prefix: #{prefix}" + raise TransformerError, "Unknown prefix: #{prefix}" end end @@ -244,7 +247,7 @@ class SearchQueryTransformer < Parslet::Transform elsif clause[:phrase] PhraseClause.new(operator, term) else - raise "Unexpected clause type: #{clause}" + raise TransformerError, "Unexpected clause type: #{clause}" end end diff --git a/lib/exceptions.rb b/lib/exceptions.rb index 18a99ace2a4..6bceac1c5e0 100644 --- a/lib/exceptions.rb +++ b/lib/exceptions.rb @@ -10,7 +10,6 @@ module Mastodon class StreamValidationError < ValidationError; end class RaceConditionError < Error; end class RateLimitExceededError < Error; end - class SyntaxError < Error; end class InvalidParameterError < Error; end class SignatureVerificationError < Error; end class MalformedHeaderError < Error; end diff --git a/spec/lib/search_query_transformer_spec.rb b/spec/lib/search_query_transformer_spec.rb index 8d0b032ddf5..540271e4f89 100644 --- a/spec/lib/search_query_transformer_spec.rb +++ b/spec/lib/search_query_transformer_spec.rb @@ -45,7 +45,7 @@ RSpec.describe SearchQueryTransformer do let(:query) { '-hello' } it 'raises an exception' do - expect { subject }.to raise_error(RuntimeError) + expect { subject }.to raise_error(SearchQueryTransformer::QueryError) end end @@ -53,7 +53,7 @@ RSpec.describe SearchQueryTransformer do let(:query) { 'after:0000' } it 'raises an exception' do - expect { subject }.to raise_error(RuntimeError) + expect { subject }.to raise_error(SearchQueryTransformer::QueryError) end end @@ -61,7 +61,7 @@ RSpec.describe SearchQueryTransformer do let(:query) { 'before:9999' } it 'raises an exception' do - expect { subject }.to raise_error(RuntimeError) + expect { subject }.to raise_error(SearchQueryTransformer::QueryError) end end @@ -69,7 +69,7 @@ RSpec.describe SearchQueryTransformer do let(:query) { 'is:reply' } it 'raises an exception' do - expect { subject }.to raise_error(RuntimeError) + expect { subject }.to raise_error(SearchQueryTransformer::QueryError) end end @@ -77,7 +77,7 @@ RSpec.describe SearchQueryTransformer do let(:query) { 'is:reply " "' } it 'raises an exception' do - expect { subject }.to raise_error(RuntimeError) + expect { subject }.to raise_error(SearchQueryTransformer::QueryError) end end diff --git a/spec/requests/api/v2/search_spec.rb b/spec/requests/api/v2/search_spec.rb index c60861b48f0..5aeeabd0515 100644 --- a/spec/requests/api/v2/search_spec.rb +++ b/spec/requests/api/v2/search_spec.rb @@ -93,12 +93,12 @@ RSpec.describe 'Search API' do end context 'when search raises syntax error' do - before { allow(Search).to receive(:new).and_raise(Mastodon::SyntaxError) } + before { allow(Search).to receive(:new).and_raise(SearchQueryTransformer::QueryError) } - it 'returns http unprocessable_content' do + it 'returns http bad request' do get '/api/v2/search', headers: headers, params: params - expect(response).to have_http_status(422) + expect(response).to have_http_status(400) expect(response.content_type) .to start_with('application/json') end