File: validator.rb

package info (click to toggle)
ruby-graphql 2.2.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,584 kB
  • sloc: ruby: 67,505; ansic: 1,753; yacc: 831; javascript: 331; makefile: 6
file content (171 lines) | stat: -rw-r--r-- 7,106 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
128
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# frozen_string_literal: true

module GraphQL
  class Schema
    class Validator
      # The thing being validated
      # @return [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class<GraphQL::Schema::InputObject>]
      attr_reader :validated

      # @param validated [GraphQL::Schema::Argument, GraphQL::Schema::Field, GraphQL::Schema::Resolver, Class<GraphQL::Schema::InputObject>] The argument or argument owner this validator is attached to
      # @param allow_blank [Boolean] if `true`, then objects that respond to `.blank?` and return true for `.blank?` will skip this validation
      # @param allow_null [Boolean] if `true`, then incoming `null`s will skip this validation
      def initialize(validated:, allow_blank: false, allow_null: false)
        @validated = validated
        @allow_blank = allow_blank
        @allow_null = allow_null
      end

      # @param object [Object] The application object that this argument's field is being resolved for
      # @param context [GraphQL::Query::Context]
      # @param value [Object] The client-provided value for this argument (after parsing and coercing by the input type)
      # @return [nil, Array<String>, String] Error message or messages to add
      def validate(object, context, value)
        raise GraphQL::RequiredImplementationMissingError, "Validator classes should implement #validate"
      end

      # This is like `String#%`, but it supports the case that only some of `string`'s
      # values are present in `substitutions`
      def partial_format(string, substitutions)
        substitutions.each do |key, value|
          sub_v = value.is_a?(String) ? value : value.to_s
          string = string.gsub("%{#{key}}", sub_v)
        end
        string
      end

      # @return [Boolean] `true` if `value` is `nil` and this validator has `allow_null: true` or if value is `.blank?` and this validator has `allow_blank: true`
      def permitted_empty_value?(value)
        (value.nil? && @allow_null) ||
          (@allow_blank && value.respond_to?(:blank?) && value.blank?)
      end

      # @param schema_member [GraphQL::Schema::Field, GraphQL::Schema::Argument, Class<GraphQL::Schema::InputObject>]
      # @param validates_hash [Hash{Symbol => Hash}, Hash{Class => Hash} nil] A configuration passed as `validates:`
      # @return [Array<Validator>]
      def self.from_config(schema_member, validates_hash)
        if validates_hash.nil? || validates_hash.empty?
          EMPTY_ARRAY
        else
          validates_hash = validates_hash.dup

          default_options = {}
          if validates_hash[:allow_null]
            default_options[:allow_null] = validates_hash.delete(:allow_null)
          end
          if validates_hash[:allow_blank]
            default_options[:allow_blank] = validates_hash.delete(:allow_blank)
          end

          # allow_nil or allow_blank are the _only_ validations:
          if validates_hash.empty?
            validates_hash = default_options
          end

          validates_hash.map do |validator_name, options|
            validator_class = case validator_name
            when Class
              validator_name
            else
              all_validators[validator_name] || raise(ArgumentError, "unknown validation: #{validator_name.inspect}")
            end
            if options.is_a?(Hash)
              validator_class.new(validated: schema_member, **(default_options.merge(options)))
            else
              validator_class.new(options, validated: schema_member, **default_options)
            end
          end
        end
      end

      # Add `validator_class` to be initialized when `validates:` is given `name`.
      # (It's initialized with whatever options are given by the key `name`).
      # @param name [Symbol]
      # @param validator_class [Class]
      # @return [void]
      def self.install(name, validator_class)
        all_validators[name] = validator_class
        nil
      end

      # Remove whatever validator class is {.install}ed at `name`, if there is one
      # @param name [Symbol]
      # @return [void]
      def self.uninstall(name)
        all_validators.delete(name)
        nil
      end

      class << self
        attr_accessor :all_validators
      end

      self.all_validators = {}

      include GraphQL::EmptyObjects

      class ValidationFailedError < GraphQL::ExecutionError
        attr_reader :errors

        def initialize(errors:)
          @errors = errors
          super(errors.join(", "))
        end
      end

      # @param validators [Array<Validator>]
      # @param object [Object]
      # @param context [Query::Context]
      # @param value [Object]
      # @return [void]
      # @raises [ValidationFailedError]
      def self.validate!(validators, object, context, value, as: nil)
        # Assuming the default case is no errors, reduce allocations in that case.
        # This will be replaced with a mutable array if we actually get any errors.
        all_errors = EMPTY_ARRAY

        validators.each do |validator|
          validated = as || validator.validated
          errors = validator.validate(object, context, value)
          if errors &&
              (errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
              (errors.is_a?(String))
            if all_errors.frozen? # It's empty
              all_errors = []
            end
            interpolation_vars = { validated: validated.graphql_name, value: value.inspect }
            if errors.is_a?(String)
              all_errors << (errors % interpolation_vars)
            else
              errors = errors.map { |e| e % interpolation_vars }
              all_errors.concat(errors)
            end
          end
        end

        if all_errors.any?
          raise ValidationFailedError.new(errors: all_errors)
        end
        nil
      end
    end
  end
end


require "graphql/schema/validator/length_validator"
GraphQL::Schema::Validator.install(:length, GraphQL::Schema::Validator::LengthValidator)
require "graphql/schema/validator/numericality_validator"
GraphQL::Schema::Validator.install(:numericality, GraphQL::Schema::Validator::NumericalityValidator)
require "graphql/schema/validator/format_validator"
GraphQL::Schema::Validator.install(:format, GraphQL::Schema::Validator::FormatValidator)
require "graphql/schema/validator/inclusion_validator"
GraphQL::Schema::Validator.install(:inclusion, GraphQL::Schema::Validator::InclusionValidator)
require "graphql/schema/validator/exclusion_validator"
GraphQL::Schema::Validator.install(:exclusion, GraphQL::Schema::Validator::ExclusionValidator)
require "graphql/schema/validator/required_validator"
GraphQL::Schema::Validator.install(:required, GraphQL::Schema::Validator::RequiredValidator)
require "graphql/schema/validator/allow_null_validator"
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)