Class: GraphQL::Schema::Validator::RequiredValidator

Inherits:
GraphQL::Schema::Validator show all
Defined in:
lib/graphql/schema/validator/required_validator.rb

Overview

Use this validator to require one of the named arguments to be present. Or, use Arrays of symbols to name a valid set of arguments.

(This is for specifying mutually exclusive sets of arguments.)

If you use GraphQL::Schema::Visibility to hide all the arguments in a one_of: [..] set, then a developer-facing Error will be raised during execution. Pass allow_all_hidden: true to skip validation in this case instead.

This validator also implements argument ... required: :nullable. If an argument has required: :nullable but it’s hidden with GraphQL::Schema::Visibility, then this validator doesn’t run.

Examples:

Require exactly one of these arguments


field :update_amount, IngredientAmount, null: false do
  argument :ingredient_id, ID, required: true
  argument :cups, Integer, required: false
  argument :tablespoons, Integer, required: false
  argument :teaspoons, Integer, required: false
  validates required: { one_of: [:cups, :tablespoons, :teaspoons] }
end

Require one of these sets of arguments


field :find_object, Node, null: true do
  argument :node_id, ID, required: false
  argument :object_type, String, required: false
  argument :object_id, Integer, required: false
  # either a global `node_id` or an `object_type`/`object_id` pair is required:
  validates required: { one_of: [:node_id, [:object_type, :object_id]] }
end

require some value for an argument, even if it’s null

field :update_settings, AccountSettings do
  # `required: :nullable` means this argument must be given, but may be `null`
  argument :age, Integer, required: :nullable
end

Constant Summary

Constants included from EmptyObjects

EmptyObjects::EMPTY_ARRAY, EmptyObjects::EMPTY_HASH

Instance Attribute Summary

Attributes inherited from GraphQL::Schema::Validator

#validated

Instance Method Summary collapse

Methods inherited from GraphQL::Schema::Validator

from_config, install, #partial_format, #permitted_empty_value?, uninstall, validate!

Constructor Details

#initialize(one_of: nil, argument: nil, allow_all_hidden: nil, message: nil, **default_options) ⇒ RequiredValidator

Returns a new instance of RequiredValidator.

Parameters:

  • one_of (Array<Symbol>) (defaults to: nil)

    A list of arguments, exactly one of which is required for this field

  • argument (Symbol) (defaults to: nil)

    An argument that is required for this field

  • allow_all_hidden (Boolean) (defaults to: nil)

    If true, then this validator won’t run if all the one_of: ... arguments have been hidden

  • message (String) (defaults to: nil)


49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/graphql/schema/validator/required_validator.rb', line 49

def initialize(one_of: nil, argument: nil, allow_all_hidden: nil, message: nil, **default_options)
  @one_of = if one_of
    one_of
  elsif argument
    [ argument ]
  else
    raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
  end
  @allow_all_hidden = allow_all_hidden.nil? ? !!argument : allow_all_hidden
  @message = message
  super(**default_options)
end

Instance Method Details

#arg_keyword_to_graphql_name(argument_definitions, arg_keyword) ⇒ Object



156
157
158
159
# File 'lib/graphql/schema/validator/required_validator.rb', line 156

def arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
  argument_definition = argument_definitions.find { |defn| defn.keyword == arg_keyword }
  argument_definition&.graphql_name
end

#build_message(context) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/graphql/schema/validator/required_validator.rb', line 129

def build_message(context)
  argument_definitions = context.types.arguments(@validated)

  required_names = @one_of.map do |arg_keyword|
    if arg_keyword.is_a?(Array)
      names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, arg) }
      names.compact! # hidden arguments are `nil`
      "(" + names.join(" and ") + ")"
    else
      arg_keyword_to_graphql_name(argument_definitions, arg_keyword)
    end
  end
  required_names.compact! # remove entries for hidden arguments


  case required_names.size
  when 0
    # The required definitions were hidden from the client.
    # Another option here would be to raise an error in the application....
    "%{validated} is missing a required argument."
  when 1
    "%{validated} must include the following argument: #{required_names.first}."
  else
    "%{validated} must include exactly one of the following arguments: #{required_names.join(", ")}."
  end
end

#validate(_object, context, value) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/graphql/schema/validator/required_validator.rb', line 62

def validate(_object, context, value)
  fully_matched_conditions = 0
  partially_matched_conditions = 0

  visible_keywords = context.types.arguments(@validated).map(&:keyword)
  no_visible_conditions = true

  if !value.nil?
    @one_of.each do |one_of_condition|
      case one_of_condition
      when Symbol
        if no_visible_conditions && visible_keywords.include?(one_of_condition)
          no_visible_conditions = false
        end

        if value.key?(one_of_condition)
          fully_matched_conditions += 1
        end
      when Array
        any_match = false
        full_match = true

        one_of_condition.each do |k|
          if no_visible_conditions && visible_keywords.include?(k)
            no_visible_conditions = false
          end
          if value.key?(k)
            any_match = true
          else
            full_match = false
          end
        end

        partial_match = !full_match && any_match

        if full_match
          fully_matched_conditions += 1
        end

        if partial_match
          partially_matched_conditions += 1
        end
      else
        raise ArgumentError, "Unknown one_of condition: #{one_of_condition.inspect}"
      end
    end
  end

  if no_visible_conditions
    if @allow_all_hidden
      return nil
    else
      raise GraphQL::Error, <<~ERR
        #{@validated.path} validates `required: ...` but all required arguments were hidden.

        Update your schema definition to allow the client to see some fields or skip validation by adding `required: { ..., allow_all_hidden: true }`
      ERR
    end
  end

  if fully_matched_conditions == 1 && partially_matched_conditions == 0
    nil # OK
  else
    @message || build_message(context)
  end
end