ruby-grape / ruby-grape/grape

Array[Array[X]]] and Array[Hash[X]]] return wrong indices in errors

Open
#1,998 7 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug?
Dominant language
Ruby
Stars
10k
Forks
1.2k
Avg merge
14h 38m
Merged PRs (30d)
92

Description

Basically the indices of nested arguments are not kept correctly during validation process. It happens both for Hashes and Arrays declared inside another Array.

In the case of Array[Array[X]] the inner index keeps increasing (in the error messages) suggesting there might be something wrong in the recursion logic of validators -- i.e., it acts as a global count.

Created the following spec to explain the issue (disclaimer: first spec file, not sure if I got it right).

# frozen_string_literal: true

require 'spec_helper'

describe Grape::Validations::DefaultValidator do
  describe '#validate!' do
    subject(:validate) { post path, params }

    module ValidationsSpec
      module DefaultValidatorSpec
        class API < Grape::API
          rescue_from Grape::Exceptions::ValidationErrors do |e|
            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)
          end

          params do
            requires :points, type: Array do
              # Following optional can be replaced with requires
              optional :location, type: Hash, allow_blank: false do
                requires :lat, type: Float, allow_blank: false
                requires :lon, type: Float, allow_blank: false
              end
            end
          end
          post '/nested-array-hash' do
          end


          params do
            requires :points, type: Array do
              # Following optional can be replaced with requires
              optional :location, type: Array, allow_blank: false do
                requires :id, type: Fixnum, allow_blank: false
              end
            end
          end
          post '/nested-array-array' do
          end

        end
      end
    end

    def app
      ValidationsSpec::DefaultValidatorSpec::API
    end

      context 'when params are nested inside hash which is inside an array' do
        let(:path) { '/nested-array-hash' }
        let(:params) {
          {
            points: [
              { location: nil },
              { location: { lat: nil, lon: 0.0 } },
              { location: { lat: "string", lon: 0.0 } },
              { location: { lat: 0.0, lon: "string" } },
              { location: { lat: 0.0, lon: nil } },
              { location: nil },
            ]
          }
        }

        it 'does return a validation error with correct array indices' do
          validate
          expect(JSON.parse(last_response.body)).to eq(
            'points[0][location]'      => ['is empty'],
            'points[1][location][lat]' => ['is empty'],
            'points[2][location][lat]' => ['is invalid'],
            'points[3][location][lon]' => ['is invalid'],
            'points[4][location][lon]' => ['is empty'],
            'points[5][location]'      => ['is empty'],
          )
        end
    end

    context 'when params are nested inside an array which is inside an array' do
      let(:path) { '/nested-array-array' }
      let(:params) {
        {
          points: [
            { location: [{ id: 0 }, { id: 1 }] }, # valid entry: to shift the indicies
            { location: [{ id: nil }, { id: 0 }] },
            { location: [{ id: 0.0 }, { id: 0 }] },
            { location: [{ id: 0 }, { id: nil }] },
            { location: [{ id: 0 }, { id: 0.0 }] },
            { location: 0 }, # This line creates multiple errors
            { location: nil },
            { location: "" }
          ]
        }
      }

      it 'does return a validation error with correct array indices' do
        validate
        expect(JSON.parse(last_response.body)).to eq(
          "points[1][location][0][id]" => ["is empty"],
          "points[2][location][0][id]" => ["is invalid"],
          "points[3][location][1][id]" => ["is empty"],
          "points[4][location][1][id]" => ["is invalid"],
          "points[5][location]" => ["is invalid"],
          "points[6][location]" => ["is empty"],
          "points[7][location]" => ["is empty"]
        )
      end
    end
  end
end

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at Grape::Validations::DefaultValidator#validate! and reproduce the issue with the supplied nested-array hash and nested-array array specs. Trace how validation errors build their keys during recursion, then verify that the expected points[index] paths are preserved for every failing parameter and that the regression specs pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
ruby
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.