we-promise/sure · warning · Api::V1::RuleRunsController::InvalidFilterError

validation_failed

validation_failed

Error message

rule_id must be a valid UUID

What it means

Api::V1::RuleRunsController#apply_filters (app/controllers/api/v1/rule_runs_controller.rb:52-55) validates the rule_id query param with valid_uuid? before using it in a where clause. A present but malformed rule_id raises the controller-local InvalidFilterError, which index rescues and renders as 422 validation_failed with message 'rule_id must be a valid UUID'. Only the format is checked, not existence.

Source

Thrown at app/controllers/api/v1/rule_runs_controller.rb:53

      raise ActiveRecord::RecordNotFound, "Rule run not found" unless valid_uuid?(params[:id])

      @rule_run = rule_runs_scope.find(params[:id])
    end

    def ensure_read_scope
      authorize_scope!(:read)
    end

    def rule_runs_scope
      RuleRun
        .joins(:rule)
        .where(rules: { family_id: current_resource_owner.family.id })
        .includes(:rule)
    end

    def apply_filters(query)
      if params[:rule_id].present?
        raise InvalidFilterError, "rule_id must be a valid UUID" unless valid_uuid?(params[:rule_id])

        query = query.where(rule_id: params[:rule_id])
      end

      if params[:status].present?
        raise InvalidFilterError, "status must be one of: #{STATUSES.join(', ')}" unless STATUSES.include?(params[:status])

        query = query.where(status: params[:status])
      end

      if params[:execution_type].present?
        unless EXECUTION_TYPES.include?(params[:execution_type])
          raise InvalidFilterError, "execution_type must be one of: #{EXECUTION_TYPES.join(', ')}"
        end

        query = query.where(execution_type: params[:execution_type])
      end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Send rule_id as a canonical UUID, e.g. ?rule_id=0e8d1ceb-5b6d-4a85-9f2e-74f3b1a2c9d4
  2. Take rule ids from GET /api/v1/rules, not from logs or UI labels
  3. Validate UUID format client-side before the request
  4. Remember an empty result set means 'no runs for that rule', not an error

Example fix

# before
GET /api/v1/rule_runs?rule_id=grocery-rule
# after
GET /api/v1/rule_runs?rule_id=0e8d1ceb-5b6d-4a85-9f2e-74f3b1a2c9d4
Defensive patterns

Strategy: validation

Validate before calling

UUID_RE = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
params[:rule_id] && !UUID_RE.match?(params[:rule_id]) and raise ArgumentError, 'rule_id must be a valid UUID'

Type guard

def valid_uuid?(v) = v.to_s.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)

Try / catch

begin
  client.get('/api/v1/rule_runs', { rule_id: rid })
rescue Faraday::UnprocessableEntity => e
  # 422 validation_failed — e.response[:body]['message'] names the bad param
end

Prevention

When it happens

Trigger: GET /api/v1/rule_runs?rule_id=abc, ?rule_id=12345, or any 8-4-4-4-12-violating string. A well-formed but non-existent rule_id does NOT error — it just returns an empty list.

Common situations: Passing a rule name, numeric id, or partially copied UUID; sending the rule's slug instead of its id; whitespace or encoding damage in the query string.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/ca65f569cf50ab1f. Report an issue: GitHub.