Skip to content

Commit

Permalink
Merge pull request #5013 from sj26/validates-all
Browse files Browse the repository at this point in the history
Allow validating a set of array values
  • Loading branch information
rmosolgo authored Jul 12, 2024
2 parents 42d012b + 35db042 commit 337eaa3
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 0 deletions.
1 change: 1 addition & 0 deletions guides/fields/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ See each validator's API docs for details:
- `required: { one_of: [...] }` {{ "Schema::Validator::RequiredValidator" | api_doc }}
- `allow_blank: true|false` {{ "Schema::Validator::AllowBlankValidator" | api_doc }}
- `allow_null: true|false` {{ "Schema::Validator::AllowNullValidator" | api_doc }}
- `all: { ... }` {{ "Schema::Validator::AllValidator" | api_doc }}

Some of the validators accept customizable messages for certain validation failures; see the API docs for examples.

Expand Down
2 changes: 2 additions & 0 deletions lib/graphql/schema/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,5 @@ def self.validate!(validators, object, context, value, as: nil)
GraphQL::Schema::Validator.install(:allow_null, GraphQL::Schema::Validator::AllowNullValidator)
require "graphql/schema/validator/allow_blank_validator"
GraphQL::Schema::Validator.install(:allow_blank, GraphQL::Schema::Validator::AllowBlankValidator)
require "graphql/schema/validator/all_validator"
GraphQL::Schema::Validator.install(:all, GraphQL::Schema::Validator::AllValidator)
60 changes: 60 additions & 0 deletions lib/graphql/schema/validator/all_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

module GraphQL
class Schema
class Validator
# Use this to validate each member of an array value.
#
# @example validate format of all strings in an array
#
# argument :handles, [String],
# validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ } } }
#
# @example multiple validators can be combined
#
# argument :handles, [String],
# validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ }, length: { maximum: 32 } } }
#
# @example any type can be used
#
# argument :choices, [Integer],
# validates: { all: { inclusion: { in: 1..12 } } }
#
class AllValidator < Validator
def initialize(validated:, allow_blank: false, allow_null: false, **validators)
super(validated: validated, allow_blank: allow_blank, allow_null: allow_null)

@validators = Validator.from_config(validated, validators)
end

def validate(object, context, value)
all_errors = EMPTY_ARRAY

value.each do |subvalue|
@validators.each do |validator|
errors = validator.validate(object, context, subvalue)
if errors &&
(errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
(errors.is_a?(String))
if all_errors.frozen? # It's empty
all_errors = []
end
if errors.is_a?(String)
all_errors << errors
else
all_errors.concat(errors)
end
end
end
end

unless all_errors.frozen?
all_errors.uniq!
end

all_errors
end
end
end
end
end
51 changes: 51 additions & 0 deletions spec/graphql/schema/validator/all_validator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true
require "spec_helper"
require_relative "./validator_helpers"

describe GraphQL::Schema::Validator::AllValidator do
include ValidatorHelpers

expectations = [
{
config: { format: { with: /\A[a-z]+\Z/ } },
cases: [
{ query: "{ validated(value: []) }", result: [], error_messages: [] },
{ query: "{ validated(value: [\"abc\"]) }", result: ["abc"], error_messages: [] },
{ query: "{ validated(value: [\"abc\", \"def\"]) }", result: ["abc", "def"], error_messages: [] },
{ query: "{ validated(value: [\"ABC\"]) }", result: nil, error_messages: ["value is invalid"] },
{ query: "{ validated(value: [\"abc\", \"DEF\"]) }", result: nil, error_messages: ["value is invalid"] },
{ query: "{ validated(value: [\"abc\", \"DEF\", \"GHI\"]) }", result: nil, error_messages: ["value is invalid"] },
],
},
{
config: { format: { with: /\A[a-z]+\Z/ }, length: { maximum: 2 } },
cases: [
{ query: "{ validated(value: []) }", result: [], error_messages: [] },
{ query: "{ validated(value: [\"a\"]) }", result: ["a"], error_messages: [] },
{ query: "{ validated(value: [\"a\", \"bc\"]) }", result: ["a", "bc"], error_messages: [] },
{ query: "{ validated(value: [\"AB\"]) }", result: nil, error_messages: ["value is invalid"] },
{ query: "{ validated(value: [\"abc\"]) }", result: nil, error_messages: ["value is too long (maximum is 2)"] },
{ query: "{ validated(value: [\"ABC\"]) }", result: nil, error_messages: ["value is invalid, value is too long (maximum is 2)"] },
{ query: "{ validated(value: [\"ABC\", \"DEF\"]) }", result: nil, error_messages: ["value is invalid, value is too long (maximum is 2)"] },
],
},
]

build_tests(:all, [String], expectations)

expectations = [
{
config: { inclusion: { in: 1..3 } },
cases: [
{ query: "{ validated(value: []) }", result: [], error_messages: [] },
{ query: "{ validated(value: [1]) }", result: [1], error_messages: [] },
{ query: "{ validated(value: [1, 2]) }", result: [1, 2], error_messages: [] },
{ query: "{ validated(value: [4]) }", result: nil, error_messages: ["value is not included in the list"] },
{ query: "{ validated(value: [1, 4]) }", result: nil, error_messages: ["value is not included in the list"] },
{ query: "{ validated(value: [1, 4, 5]) }", result: nil, error_messages: ["value is not included in the list"] },
],
},
]

build_tests(:all, [Integer], expectations)
end

0 comments on commit 337eaa3

Please sign in to comment.