Skip to content

Creating custom validators is difficult #2012

Closed
@stanhu

Description

@stanhu

I'm attempting to port a custom validator to Grape v1.3.1:

requires :custom, type: ::API::Validations::Types::CustomObject, desc: 'This is a test'

At first, I tried to take https://github.com/ruby-grape/grape/blob/41ee988c06b87b036d12c4c7247d06b75bfa0b7f/lib/grape/validations/types/file.rb as an example:

module API
  module Validations
    module Types
      class CustomObject
        def coerce(input)
          input
        end

        def coerced?(value)
          value.is_a?(::CustomObject)
        end
      end
    end
  end
end

In Grape v1.1.0, this worked (ignore the coerced_value? vs.coerced? change and lack of inheritance of Virtus::Attribute) because BuildCoercer.create_coercer_instance would just fall through and create a Virtus Attribute: https://github.com/ruby-grape/grape/blob/v1.1.0/lib/grape/validations/types/build_coercer.rb#L71.

In Grape v1.3.1, with the switch to dry-types, all unknown types fall through to PrimitiveCoercer and fails:

  1. PrimitiveCoercer.new type, strict
    .
  2. This quietly fails validation because super in eventually calls @coercer[val] in .
  3. This fails because @coercer is nil.

To get around this, I saw the custom? check appears to look for self.parse:

type.respond_to?(:parse) &&
type.method(:parse).arity == 1
. I ended up with:

module API
  module Validations
    module Types
      class CustomObject
        def self.coerce(input)
          input
        end

        def self.coerced?(value)
          value.is_a?(::CustomObject)
        end

        def self.parse(value)
          value
        end
      end
    end
  end
end

This appears to work for my purposes, but it seems inconsistent (not to mention undocumented) compared to the built-in validators present in Grape.

Note that dry-types has a simple mechanism for checking types: https://dry-rb.org/gems/dry-types/master/custom-types. Ideally, we'd be able to pass in one of these custom types so we don't need all that code above, but this doesn't work either for the same reasons above:

requires :custom, type: DryTypes.Instance(Range), desc: 'This is a test'

@dnesteryuk My questions are:

  1. Should the Grape type validations look more consistent between Json, File, and custom ones (e.g. class vs. instance methods, self.parse method)?
  2. If so, how can we make them more consistent? Otherwise, is this just a documentation issue?
  3. Should Grape handle custom dry-types (e.g. https://dry-rb.org/gems/dry-types/1.2/custom-types) natively?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions