graphql 2.1.1 → 2.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/graphql/analysis/ast/query_depth.rb +7 -2
- data/lib/graphql/dataloader.rb +29 -10
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +1 -1
- data/lib/graphql/execution/interpreter/runtime.rb +7 -26
- data/lib/graphql/execution/lookahead.rb +87 -20
- data/lib/graphql/load_application_object_failed_error.rb +5 -1
- data/lib/graphql/pagination/connection.rb +15 -10
- data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
- data/lib/graphql/query/context.rb +4 -0
- data/lib/graphql/query/null_context.rb +2 -10
- data/lib/graphql/query.rb +10 -0
- data/lib/graphql/schema/build_from_definition.rb +0 -11
- data/lib/graphql/schema/directive/one_of.rb +12 -0
- data/lib/graphql/schema/directive.rb +1 -1
- data/lib/graphql/schema/enum.rb +2 -2
- data/lib/graphql/schema/field/scope_extension.rb +4 -3
- data/lib/graphql/schema/field.rb +2 -2
- data/lib/graphql/schema/has_single_input_argument.rb +2 -2
- data/lib/graphql/schema/input_object.rb +1 -1
- data/lib/graphql/schema/interface.rb +10 -10
- data/lib/graphql/schema/loader.rb +0 -2
- data/lib/graphql/schema/member/has_arguments.rb +47 -36
- data/lib/graphql/schema/member/has_fields.rb +4 -4
- data/lib/graphql/schema/member/has_interfaces.rb +2 -2
- data/lib/graphql/schema/member/validates_input.rb +3 -3
- data/lib/graphql/schema/resolver.rb +3 -3
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/warden.rb +73 -57
- data/lib/graphql/schema.rb +154 -43
- data/lib/graphql/subscriptions/event.rb +1 -1
- data/lib/graphql/subscriptions.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- metadata +17 -17
data/lib/graphql/schema.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "logger"
|
2
3
|
require "graphql/schema/addition"
|
3
4
|
require "graphql/schema/always_visible"
|
4
5
|
require "graphql/schema/base_64_encoder"
|
@@ -146,6 +147,19 @@ module GraphQL
|
|
146
147
|
@subscriptions = new_implementation
|
147
148
|
end
|
148
149
|
|
150
|
+
# @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set.
|
151
|
+
def default_trace_mode(new_mode = nil)
|
152
|
+
if new_mode
|
153
|
+
@default_trace_mode = new_mode
|
154
|
+
elsif defined?(@default_trace_mode)
|
155
|
+
@default_trace_mode
|
156
|
+
elsif superclass.respond_to?(:default_trace_mode)
|
157
|
+
superclass.default_trace_mode
|
158
|
+
else
|
159
|
+
:default
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
149
163
|
def trace_class(new_class = nil)
|
150
164
|
if new_class
|
151
165
|
trace_mode(:default, new_class)
|
@@ -158,41 +172,65 @@ module GraphQL
|
|
158
172
|
|
159
173
|
# @return [Class] Return the trace class to use for this mode, looking one up on the superclass if this Schema doesn't have one defined.
|
160
174
|
def trace_class_for(mode)
|
161
|
-
|
162
|
-
|
163
|
-
case mode
|
164
|
-
when :default
|
165
|
-
superclass_base_class = if superclass.respond_to?(:trace_class_for)
|
166
|
-
superclass.trace_class_for(mode)
|
167
|
-
else
|
168
|
-
GraphQL::Tracing::Trace
|
169
|
-
end
|
170
|
-
Class.new(superclass_base_class)
|
171
|
-
when :default_backtrace
|
172
|
-
schema_base_class = trace_class_for(:default)
|
173
|
-
Class.new(schema_base_class) do
|
174
|
-
include(GraphQL::Backtrace::Trace)
|
175
|
-
end
|
176
|
-
else
|
177
|
-
mods = trace_modules_for(mode)
|
178
|
-
Class.new(trace_class_for(:default)) do
|
179
|
-
mods.any? && include(*mods)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
175
|
+
own_trace_modes[mode] ||
|
176
|
+
(superclass.respond_to?(:trace_class_for) ? superclass.trace_class_for(mode) : (own_trace_modes[mode] = build_trace_mode(mode)))
|
183
177
|
end
|
184
178
|
|
185
179
|
# Configure `trace_class` to be used whenever `context: { trace_mode: mode_name }` is requested.
|
186
|
-
#
|
180
|
+
# {default_trace_mode} is used when no `trace_mode: ...` is requested.
|
181
|
+
#
|
182
|
+
# When a `trace_class` is added this way, it will _not_ receive other modules added with `trace_with(...)`
|
183
|
+
# unless `trace_mode` is explicitly given. (This class will not recieve any default trace modules.)
|
184
|
+
#
|
185
|
+
# Subclasses of the schema will use `trace_class` as a base class for this mode and those
|
186
|
+
# subclass also will _not_ receive default tracing modules.
|
187
|
+
#
|
187
188
|
# @param mode_name [Symbol]
|
188
189
|
# @param trace_class [Class] subclass of GraphQL::Tracing::Trace
|
189
190
|
# @return void
|
190
191
|
def trace_mode(mode_name, trace_class)
|
191
|
-
|
192
|
-
@trace_modes[mode_name] = trace_class
|
192
|
+
own_trace_modes[mode_name] = trace_class
|
193
193
|
nil
|
194
194
|
end
|
195
195
|
|
196
|
+
def own_trace_modes
|
197
|
+
@own_trace_modes ||= {}
|
198
|
+
end
|
199
|
+
|
200
|
+
module DefaultTraceClass
|
201
|
+
end
|
202
|
+
|
203
|
+
private_constant :DefaultTraceClass
|
204
|
+
|
205
|
+
def build_trace_mode(mode)
|
206
|
+
case mode
|
207
|
+
when :default
|
208
|
+
# Use the superclass's default mode if it has one, or else start an inheritance chain at the built-in base class.
|
209
|
+
base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || GraphQL::Tracing::Trace
|
210
|
+
Class.new(base_class) do
|
211
|
+
include DefaultTraceClass
|
212
|
+
end
|
213
|
+
when :default_backtrace
|
214
|
+
schema_base_class = trace_class_for(:default)
|
215
|
+
Class.new(schema_base_class) do
|
216
|
+
include(GraphQL::Backtrace::Trace)
|
217
|
+
end
|
218
|
+
else
|
219
|
+
# First, see if the superclass has a custom-defined class for this.
|
220
|
+
# Then, if it doesn't, use this class's default trace
|
221
|
+
base_class = (superclass.respond_to?(:trace_class_for) && superclass.trace_class_for(mode)) || trace_class_for(:default)
|
222
|
+
# Prepare the default trace class if it hasn't been initialized yet
|
223
|
+
base_class ||= (own_trace_modes[:default] = build_trace_mode(:default))
|
224
|
+
mods = trace_modules_for(mode)
|
225
|
+
if base_class < DefaultTraceClass
|
226
|
+
mods = trace_modules_for(:default) + mods
|
227
|
+
end
|
228
|
+
Class.new(base_class) do
|
229
|
+
mods.any? && include(*mods)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
196
234
|
def own_trace_modules
|
197
235
|
@own_trace_modules ||= Hash.new { |h, k| h[k] = [] }
|
198
236
|
end
|
@@ -288,7 +326,7 @@ module GraphQL
|
|
288
326
|
# Build a map of `{ name => type }` and return it
|
289
327
|
# @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
|
290
328
|
# @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
|
291
|
-
def types(context = GraphQL::Query::NullContext)
|
329
|
+
def types(context = GraphQL::Query::NullContext.instance)
|
292
330
|
all_types = non_introspection_types.merge(introspection_system.types)
|
293
331
|
visible_types = {}
|
294
332
|
all_types.each do |k, v|
|
@@ -315,7 +353,7 @@ module GraphQL
|
|
315
353
|
|
316
354
|
# @param type_name [String]
|
317
355
|
# @return [Module, nil] A type, or nil if there's no type called `type_name`
|
318
|
-
def get_type(type_name, context = GraphQL::Query::NullContext)
|
356
|
+
def get_type(type_name, context = GraphQL::Query::NullContext.instance)
|
319
357
|
local_entry = own_types[type_name]
|
320
358
|
type_defn = case local_entry
|
321
359
|
when nil
|
@@ -446,7 +484,7 @@ module GraphQL
|
|
446
484
|
# @param type [Module] The type definition whose possible types you want to see
|
447
485
|
# @return [Hash<String, Module>] All possible types, if no `type` is given.
|
448
486
|
# @return [Array<Module>] Possible types for `type`, if it's given.
|
449
|
-
def possible_types(type = nil, context = GraphQL::Query::NullContext)
|
487
|
+
def possible_types(type = nil, context = GraphQL::Query::NullContext.instance)
|
450
488
|
if type
|
451
489
|
# TODO duck-typing `.possible_types` would probably be nicer here
|
452
490
|
if type.kind.union?
|
@@ -499,18 +537,17 @@ module GraphQL
|
|
499
537
|
attr_writer :dataloader_class
|
500
538
|
|
501
539
|
def references_to(to_type = nil, from: nil)
|
502
|
-
@own_references_to ||=
|
540
|
+
@own_references_to ||= {}
|
503
541
|
if to_type
|
504
542
|
if !to_type.is_a?(String)
|
505
543
|
to_type = to_type.graphql_name
|
506
544
|
end
|
507
545
|
|
508
546
|
if from
|
509
|
-
@own_references_to[to_type]
|
547
|
+
refs = @own_references_to[to_type] ||= []
|
548
|
+
refs << from
|
510
549
|
else
|
511
|
-
|
512
|
-
inherited_refs = find_inherited_value(:references_to, EMPTY_HASH)[to_type] || EMPTY_ARRAY
|
513
|
-
own_refs + inherited_refs
|
550
|
+
get_references_to(to_type) || EMPTY_ARRAY
|
514
551
|
end
|
515
552
|
else
|
516
553
|
# `@own_references_to` can be quite large for big schemas,
|
@@ -530,7 +567,7 @@ module GraphQL
|
|
530
567
|
GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
|
531
568
|
end
|
532
569
|
|
533
|
-
def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext)
|
570
|
+
def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance)
|
534
571
|
parent_type = case type_or_name
|
535
572
|
when LateBoundType
|
536
573
|
get_type(type_or_name.name, context)
|
@@ -553,7 +590,7 @@ module GraphQL
|
|
553
590
|
end
|
554
591
|
end
|
555
592
|
|
556
|
-
def get_fields(type, context = GraphQL::Query::NullContext)
|
593
|
+
def get_fields(type, context = GraphQL::Query::NullContext.instance)
|
557
594
|
type.fields(context)
|
558
595
|
end
|
559
596
|
|
@@ -703,9 +740,10 @@ module GraphQL
|
|
703
740
|
|
704
741
|
attr_writer :max_depth
|
705
742
|
|
706
|
-
def max_depth(new_max_depth = nil)
|
743
|
+
def max_depth(new_max_depth = nil, count_introspection_fields: true)
|
707
744
|
if new_max_depth
|
708
745
|
@max_depth = new_max_depth
|
746
|
+
@count_introspection_fields = count_introspection_fields
|
709
747
|
elsif defined?(@max_depth)
|
710
748
|
@max_depth
|
711
749
|
else
|
@@ -713,6 +751,14 @@ module GraphQL
|
|
713
751
|
end
|
714
752
|
end
|
715
753
|
|
754
|
+
def count_introspection_fields
|
755
|
+
if defined?(@count_introspection_fields)
|
756
|
+
@count_introspection_fields
|
757
|
+
else
|
758
|
+
find_inherited_value(:count_introspection_fields, true)
|
759
|
+
end
|
760
|
+
end
|
761
|
+
|
716
762
|
def disable_introspection_entry_points
|
717
763
|
@disable_introspection_entry_points = true
|
718
764
|
# TODO: this clears the cache made in `def types`. But this is not a great solution.
|
@@ -790,6 +836,26 @@ module GraphQL
|
|
790
836
|
end
|
791
837
|
end
|
792
838
|
|
839
|
+
def default_logger(new_default_logger = NOT_CONFIGURED)
|
840
|
+
if NOT_CONFIGURED.equal?(new_default_logger)
|
841
|
+
if defined?(@default_logger)
|
842
|
+
@default_logger
|
843
|
+
elsif superclass.respond_to?(:default_logger)
|
844
|
+
superclass.default_logger
|
845
|
+
elsif defined?(Rails) && Rails.respond_to?(:logger) && (rails_logger = Rails.logger)
|
846
|
+
rails_logger
|
847
|
+
else
|
848
|
+
def_logger = Logger.new($stdout)
|
849
|
+
def_logger.info! # It doesn't output debug info by default
|
850
|
+
def_logger
|
851
|
+
end
|
852
|
+
elsif new_default_logger == nil
|
853
|
+
@default_logger = Logger.new(IO::NULL)
|
854
|
+
else
|
855
|
+
@default_logger = new_default_logger
|
856
|
+
end
|
857
|
+
end
|
858
|
+
|
793
859
|
def context_class(new_context_class = nil)
|
794
860
|
if new_context_class
|
795
861
|
@context_class = new_context_class
|
@@ -876,6 +942,12 @@ module GraphQL
|
|
876
942
|
def inherited(child_class)
|
877
943
|
if self == GraphQL::Schema
|
878
944
|
child_class.directives(default_directives.values)
|
945
|
+
child_class.extend(SubclassGetReferencesTo)
|
946
|
+
end
|
947
|
+
# Make sure the child class has these built out, so that
|
948
|
+
# subclasses can be modified by later calls to `trace_with`
|
949
|
+
own_trace_modes.each do |name, _class|
|
950
|
+
child_class.own_trace_modes[name] = child_class.build_trace_mode(name)
|
879
951
|
end
|
880
952
|
child_class.singleton_class.prepend(ResolveTypeWithType)
|
881
953
|
super
|
@@ -999,7 +1071,8 @@ module GraphQL
|
|
999
1071
|
end
|
1000
1072
|
|
1001
1073
|
def tracer(new_tracer)
|
1002
|
-
|
1074
|
+
default_trace = trace_class_for(:default)
|
1075
|
+
if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
|
1003
1076
|
trace_with(GraphQL::Tracing::CallLegacyTracers)
|
1004
1077
|
end
|
1005
1078
|
|
@@ -1021,10 +1094,20 @@ module GraphQL
|
|
1021
1094
|
if mode.is_a?(Array)
|
1022
1095
|
mode.each { |m| trace_with(trace_mod, mode: m, **options) }
|
1023
1096
|
else
|
1024
|
-
tc =
|
1097
|
+
tc = own_trace_modes[mode] ||= build_trace_mode(mode)
|
1025
1098
|
tc.include(trace_mod)
|
1026
|
-
|
1027
|
-
|
1099
|
+
own_trace_modules[mode] << trace_mod
|
1100
|
+
|
1101
|
+
if mode == :default
|
1102
|
+
# This module is being added as a default tracer. If any other mode classes
|
1103
|
+
# have already been created, but get their default behavior from a superclass,
|
1104
|
+
# Then mix this into this schema's subclass.
|
1105
|
+
# (But don't mix it into mode classes that aren't default-based.)
|
1106
|
+
own_trace_modes.each do |other_mode_name, other_mode_class|
|
1107
|
+
if other_mode_class < DefaultTraceClass && !(other_mode_class < trace_mod)
|
1108
|
+
other_mode_class.include(trace_mod)
|
1109
|
+
end
|
1110
|
+
end
|
1028
1111
|
end
|
1029
1112
|
t_opts = trace_options_for(mode)
|
1030
1113
|
t_opts.merge!(options)
|
@@ -1047,6 +1130,8 @@ module GraphQL
|
|
1047
1130
|
|
1048
1131
|
# Create a trace instance which will include the trace modules specified for the optional mode.
|
1049
1132
|
#
|
1133
|
+
# If no `mode:` is given, then {default_trace_mode} will be used.
|
1134
|
+
#
|
1050
1135
|
# @param mode [Symbol] Trace modules for this trade mode will be included
|
1051
1136
|
# @param options [Hash] Keywords that will be passed to the tracing class during `#initialize`
|
1052
1137
|
# @return [Tracing::Trace]
|
@@ -1057,14 +1142,19 @@ module GraphQL
|
|
1057
1142
|
trace_mode = if mode
|
1058
1143
|
mode
|
1059
1144
|
elsif target && target.context[:backtrace]
|
1060
|
-
:
|
1145
|
+
if default_trace_mode != :default
|
1146
|
+
raise ArgumentError, "Can't use `context[:backtrace]` with a custom default trace mode (`#{dm.inspect}`)"
|
1147
|
+
else
|
1148
|
+
own_trace_modes[:default_backtrace] ||= build_trace_mode(:default_backtrace)
|
1149
|
+
:default_backtrace
|
1150
|
+
end
|
1061
1151
|
else
|
1062
|
-
|
1152
|
+
default_trace_mode
|
1063
1153
|
end
|
1064
1154
|
|
1065
1155
|
base_trace_options = trace_options_for(trace_mode)
|
1066
1156
|
trace_options = base_trace_options.merge(options)
|
1067
|
-
trace_class_for_mode = trace_class_for(trace_mode)
|
1157
|
+
trace_class_for_mode = trace_class_for(trace_mode) || raise(ArgumentError, "#{self} has no trace class for mode: #{trace_mode.inspect}")
|
1068
1158
|
trace_class_for_mode.new(**trace_options)
|
1069
1159
|
end
|
1070
1160
|
|
@@ -1328,6 +1418,27 @@ module GraphQL
|
|
1328
1418
|
def own_multiplex_analyzers
|
1329
1419
|
@own_multiplex_analyzers ||= []
|
1330
1420
|
end
|
1421
|
+
|
1422
|
+
# This is overridden in subclasses to check the inheritance chain
|
1423
|
+
def get_references_to(type_name)
|
1424
|
+
@own_references_to[type_name]
|
1425
|
+
end
|
1426
|
+
end
|
1427
|
+
|
1428
|
+
module SubclassGetReferencesTo
|
1429
|
+
def get_references_to(type_name)
|
1430
|
+
own_refs = @own_references_to[type_name]
|
1431
|
+
inherited_refs = superclass.references_to(type_name)
|
1432
|
+
if inherited_refs&.any?
|
1433
|
+
if own_refs&.any?
|
1434
|
+
own_refs + inherited_refs
|
1435
|
+
else
|
1436
|
+
inherited_refs
|
1437
|
+
end
|
1438
|
+
else
|
1439
|
+
own_refs
|
1440
|
+
end
|
1441
|
+
end
|
1331
1442
|
end
|
1332
1443
|
|
1333
1444
|
# Install these here so that subclasses will also install it.
|
@@ -37,7 +37,7 @@ module GraphQL
|
|
37
37
|
end
|
38
38
|
|
39
39
|
# @return [String] an identifier for this unit of subscription
|
40
|
-
def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext)
|
40
|
+
def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext.instance)
|
41
41
|
subscription = field.resolver || GraphQL::Schema::Subscription
|
42
42
|
normalized_args = stringify_args(field, arguments.to_h, context)
|
43
43
|
subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
|
@@ -62,7 +62,7 @@ module GraphQL
|
|
62
62
|
# @return [void]
|
63
63
|
def trigger(event_name, args, object, scope: nil, context: {})
|
64
64
|
# Make something as context-like as possible, even though there isn't a current query:
|
65
|
-
dummy_query = GraphQL::Query.new(@schema, "", validate: false, context: context)
|
65
|
+
dummy_query = GraphQL::Query.new(@schema, "{ __typename }", validate: false, context: context)
|
66
66
|
context = dummy_query.context
|
67
67
|
event_name = event_name.to_s
|
68
68
|
|
@@ -83,7 +83,7 @@ module GraphQL
|
|
83
83
|
|
84
84
|
# Normalize symbol-keyed args to strings, try camelizing them
|
85
85
|
# Should this accept a real context somehow?
|
86
|
-
normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext)
|
86
|
+
normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext.instance)
|
87
87
|
|
88
88
|
event = Subscriptions::Event.new(
|
89
89
|
name: normalized_event_name,
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-10-
|
11
|
+
date: 2023-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: racc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.4'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: benchmark-ips
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,20 +108,6 @@ dependencies:
|
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '1.0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: racc
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '1.4'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '1.4'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: rake
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -619,7 +619,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
619
619
|
requirements:
|
620
620
|
- - ">="
|
621
621
|
- !ruby/object:Gem::Version
|
622
|
-
version: 2.
|
622
|
+
version: 2.7.0
|
623
623
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
624
624
|
requirements:
|
625
625
|
- - ">="
|