graphiti 1.2.33 → 1.2.38

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fd3e0460824b4c80d8bf5e1cb798b94a297bdb39ab077cb655b9fe085779bd2
4
- data.tar.gz: 3ae7e9ef0209b9bd0c823cd323bc3f066f2116a807ad66f714d3c63205351649
3
+ metadata.gz: c269e7e8afb7836e7d20aa4d8f837f10ef8576356da1d8c76b93b40bc630a97c
4
+ data.tar.gz: 29ffb9ec585198d92ab8cb75f23562f96ad8f82115301844b389374cbc94cd67
5
5
  SHA512:
6
- metadata.gz: 8e294afd7a47b32fbc4aa0ba695aafbeb43f42bf4c32d6f05eb2a182bca4e6b0ab0bdd68ad8a54dda8b7cee24636b0097b52629895adb758c3d2b148e3dda7c7
7
- data.tar.gz: 8454b4433f15af9464d9b708dad9cb16dff9fb368085b1297aa733f5a8dcb56a0621f4cfdbb63bfe06931d24bd17378c42655059b68a763025a33a5903605c34
6
+ metadata.gz: a5cfe7c03f2a66e3f874902030e56aed8561ed81b4dd5bca849bee82b902d908f4faa32131842220fa1b010b9c2b2276b9158a852e2cf58ddf6f4b5118769dd1
7
+ data.tar.gz: 39b5ffbfa860fcdf1601c2f718c75d401bb2851a1c1c496f5930a81d64329845bba0d4fa7312128d8da25bfe07624fc2be777ccaf768257ae4d6d0291d6796d8
data/CHANGELOG.md CHANGED
@@ -1,6 +1,7 @@
1
1
  ## Unreleased
2
2
 
3
3
  Features:
4
+ - [329](https://github.com/graphiti-api/graphiti/pull/329) Propagate `extra_fields` to related resource links.
4
5
  - [242](https://github.com/graphiti-api/graphiti/pull/242) Bump `jsonapi-renderer` to `~0.2.2` now that (https://github.com/jsonapi-rb/jsonapi-renderer/pull/36) is fixed.
5
6
  - [158](https://github.com/graphiti-api/graphiti/pull/158) Filters options `allow_nil: true`
6
7
  Option can be set at the resource level `Resource.filters_accept_nil_by_default = true`.
data/lib/graphiti.rb CHANGED
@@ -142,6 +142,7 @@ require "graphiti/scoping/sort"
142
142
  require "graphiti/scoping/paginate"
143
143
  require "graphiti/scoping/extra_attributes"
144
144
  require "graphiti/scoping/filterable"
145
+ require "graphiti/scoping/filter_group_validator"
145
146
  require "graphiti/scoping/default_filter"
146
147
  require "graphiti/scoping/filter"
147
148
  require "graphiti/stats/dsl"
@@ -14,13 +14,12 @@ module Graphiti
14
14
  end
15
15
 
16
16
  class NullRelation
17
- extend ActiveModel::Naming
18
17
  attr_accessor :id, :errors, :pointer
19
18
 
20
19
  def initialize(id, pointer)
21
20
  @id = id
22
21
  @pointer = pointer
23
- @errors = ActiveModel::Errors.new(self)
22
+ @errors = Graphiti::Util::SimpleErrors.new(self)
24
23
  end
25
24
 
26
25
  def self.human_attribute_name(attr, options = {})
@@ -817,5 +816,34 @@ module Graphiti
817
816
 
818
817
  class ConflictRequest < InvalidRequest
819
818
  end
819
+
820
+ class FilterGroupInvalidRequirement < Base
821
+ def initialize(resource, valid_required_values)
822
+ @resource = resource
823
+ @valid_required_values = valid_required_values
824
+ end
825
+
826
+ def message
827
+ <<-MSG.gsub(/\s+/, " ").strip
828
+ The filter group required: value on resource #{@resource.class} must be one of the following:
829
+ #{@valid_required_values.join(", ")}
830
+ MSG
831
+ end
832
+ end
833
+
834
+ class FilterGroupMissingRequiredFilters < Base
835
+ def initialize(resource, filter_names, required)
836
+ @resource = resource
837
+ @filter_names = filter_names
838
+ @required_label = required == :all ? "All" : "One"
839
+ end
840
+
841
+ def message
842
+ <<-MSG.gsub(/\s+/, " ").strip
843
+ #{@required_label} of the following filters must be provided on resource #{@resource.type}:
844
+ #{@filter_names.join(", ")}
845
+ MSG
846
+ end
847
+ end
820
848
  end
821
849
  end
@@ -46,9 +46,9 @@ module Graphiti
46
46
  next false unless instance_exec(&options[:if])
47
47
  end
48
48
 
49
- @extra_fields &&
50
- @extra_fields[@_type] &&
51
- @extra_fields[@_type].include?(name)
49
+ next false unless @extra_fields
50
+
51
+ @extra_fields[@_type]&.include?(name) || @extra_fields[@resource&.type]&.include?(name)
52
52
  }
53
53
 
54
54
  attribute name, if: allow_field, &blk
@@ -74,19 +74,22 @@ module Graphiti
74
74
  name_chain << k unless name_chain.last == k
75
75
 
76
76
  unless remote_resource? && serializers.nil?
77
- attrs[name.to_sym] = if serializers.is_a?(Array)
78
- serializers.map do |rr|
77
+ payload = if serializers.is_a?(Array)
78
+ data = serializers.map { |rr|
79
79
  rr.to_hash(fields: fields, include: nested_include, graphql: graphql, name_chain: name_chain)
80
- end
80
+ }
81
+ graphql ? {nodes: data} : data
81
82
  elsif serializers.nil?
82
83
  if @resource.class.respond_to?(:sideload)
83
84
  if @resource.class.sideload(k).type.to_s.include?("_many")
84
- []
85
+ graphql ? {nodes: []} : []
85
86
  end
86
87
  end
87
88
  else
88
89
  serializers.to_hash(fields: fields, include: nested_include, graphql: graphql, name_chain: name_chain)
89
90
  end
91
+
92
+ attrs[name.to_sym] = payload
90
93
  end
91
94
  end
92
95
 
@@ -133,29 +136,58 @@ module Graphiti
133
136
  serializers = options[:data]
134
137
  opts = options.slice(:fields, :include)
135
138
  opts[:graphql] = @graphql
136
- to_hash(serializers, opts).tap do |hash|
137
- hash.merge!(options.slice(:meta)) unless options[:meta].empty?
138
- end
139
+ top_level_key = get_top_level_key(@resource, serializers.is_a?(Array))
140
+
141
+ hash = {top_level_key => {}}
142
+ nodes = get_nodes(serializers, opts)
143
+ add_nodes(hash, top_level_key, options, nodes, @graphql)
144
+ add_stats(hash, top_level_key, options, @graphql)
145
+ hash
139
146
  end
140
147
 
141
148
  private
142
149
 
143
- def to_hash(serializers, opts)
144
- {}.tap do |hash|
145
- top_level_key = :data
146
- if @graphql
147
- top_level_key = @resource.graphql_entrypoint
148
- unless serializers.is_a?(Array)
149
- top_level_key = top_level_key.to_s.singularize.to_sym
150
- end
150
+ def get_top_level_key(resource, is_many)
151
+ key = :data
152
+
153
+ if @graphql
154
+ key = @resource.graphql_entrypoint
155
+ key = key.to_s.singularize.to_sym unless is_many
156
+ end
157
+
158
+ key
159
+ end
160
+
161
+ def get_nodes(serializers, opts)
162
+ if serializers.is_a?(Array)
163
+ serializers.map do |s|
164
+ s.to_hash(**opts)
151
165
  end
166
+ else
167
+ serializers.to_hash(**opts)
168
+ end
169
+ end
170
+
171
+ def add_nodes(hash, top_level_key, opts, nodes, graphql)
172
+ payload = nodes
173
+ if graphql && nodes.is_a?(Array)
174
+ payload = {nodes: nodes}
175
+ end
152
176
 
153
- hash[top_level_key] = if serializers.is_a?(Array)
154
- serializers.map do |s|
155
- s.to_hash(**opts)
177
+ # Don't render nodes if we only requested stats
178
+ unless graphql && opts[:fields].values == [[:stats]]
179
+ hash[top_level_key] = payload
180
+ end
181
+ end
182
+
183
+ def add_stats(hash, top_level_key, options, graphql)
184
+ if options[:meta] && !options[:meta].empty?
185
+ if @graphql
186
+ if (stats = options[:meta][:stats])
187
+ hash[top_level_key][:stats] = stats
156
188
  end
157
189
  else
158
- serializers.to_hash(**opts)
190
+ hash.merge!(options.slice(:meta))
159
191
  end
160
192
  end
161
193
  end
@@ -26,17 +26,17 @@ module Graphiti
26
26
  [:data, :type],
27
27
  [:data, :id]
28
28
  ].each do |required_attr|
29
- attribute_mismatch(required_attr) unless @raw_params.dig(*required_attr)
29
+ attribute_mismatch(required_attr) unless @params.dig(*required_attr)
30
30
  end
31
31
  errors.blank?
32
32
  end
33
33
 
34
34
  def payload_matches_endpoint?
35
- unless @raw_params.dig(:data, :id) == @raw_params.dig(:filter, :id)
35
+ unless @params.dig(:data, :id) == @params.dig(:filter, :id)
36
36
  attribute_mismatch([:data, :id])
37
37
  end
38
38
 
39
- meta_type = @raw_params.dig(:data, :type)
39
+ meta_type = @params.dig(:data, :type)
40
40
 
41
41
  # NOTE: calling #to_s and comparing 2 strings is slower than
42
42
  # calling #to_sym and comparing 2 symbols. But pre ruby-2.2
@@ -5,21 +5,31 @@ module Graphiti
5
5
 
6
6
  def initialize(root_resource, raw_params, action)
7
7
  @root_resource = root_resource
8
- @raw_params = raw_params
8
+ @params = normalized_params(raw_params)
9
9
  @errors = Graphiti::Util::SimpleErrors.new(raw_params)
10
10
  @action = action
11
11
  end
12
12
 
13
13
  def validate
14
+ # Right now, all requests - even reads - go through the validator
15
+ # In the future these should have their own validation logic, but
16
+ # for now we can just bypass
17
+ return true unless @params.has_key?(:data)
18
+
14
19
  resource = @root_resource
15
- if (meta_type = deserialized_payload.meta[:type].try(:to_sym))
16
- if @root_resource.type != meta_type && @root_resource.polymorphic?
17
- resource = @root_resource.class.resource_for_type(meta_type).new
20
+
21
+ if @params[:data].has_key?(:type)
22
+ if (meta_type = deserialized_payload.meta[:type].try(:to_sym))
23
+ if @root_resource.type != meta_type && @root_resource.polymorphic?
24
+ resource = @root_resource.class.resource_for_type(meta_type).new
25
+ end
18
26
  end
19
- end
20
27
 
21
- typecast_attributes(resource, deserialized_payload.attributes, deserialized_payload.meta[:payload_path])
22
- process_relationships(resource, deserialized_payload.relationships, deserialized_payload.meta[:payload_path])
28
+ typecast_attributes(resource, deserialized_payload.attributes, deserialized_payload.meta[:payload_path])
29
+ process_relationships(resource, deserialized_payload.relationships, deserialized_payload.meta[:payload_path])
30
+ else
31
+ errors.add(:"data.type", :missing)
32
+ end
23
33
 
24
34
  errors.blank?
25
35
  end
@@ -33,14 +43,7 @@ module Graphiti
33
43
  end
34
44
 
35
45
  def deserialized_payload
36
- @deserialized_payload ||= begin
37
- payload = normalized_params
38
- if payload[:data] && payload[:data][:type]
39
- Graphiti::Deserializer.new(payload)
40
- else
41
- Graphiti::Deserializer.new({})
42
- end
43
- end
46
+ @deserialized_payload ||= Graphiti::Deserializer.new(@params)
44
47
  end
45
48
 
46
49
  private
@@ -86,8 +89,8 @@ module Graphiti
86
89
  end
87
90
  end
88
91
 
89
- def normalized_params
90
- normalized = @raw_params
92
+ def normalized_params(raw_params)
93
+ normalized = raw_params
91
94
  if normalized.respond_to?(:to_unsafe_h)
92
95
  normalized = normalized.to_unsafe_h.deep_symbolize_keys
93
96
  end
@@ -199,6 +199,7 @@ module Graphiti
199
199
  @config ||=
200
200
  {
201
201
  filters: {},
202
+ grouped_filters: {},
202
203
  default_filters: {},
203
204
  stats: {},
204
205
  sort_all: nil,
@@ -235,6 +236,10 @@ module Graphiti
235
236
  config[:filters]
236
237
  end
237
238
 
239
+ def grouped_filters
240
+ config[:grouped_filters]
241
+ end
242
+
238
243
  def sorts
239
244
  config[:sorts]
240
245
  end
@@ -273,6 +278,10 @@ module Graphiti
273
278
  self.class.filters
274
279
  end
275
280
 
281
+ def grouped_filters
282
+ self.class.grouped_filters
283
+ end
284
+
276
285
  def sort_all
277
286
  self.class.sort_all
278
287
  end
@@ -44,6 +44,17 @@ module Graphiti
44
44
  end
45
45
  end
46
46
 
47
+ def filter_group(filter_names, *args)
48
+ opts = args.extract_options!
49
+
50
+ Scoping::FilterGroupValidator.raise_unless_filter_group_requirement_valid!(self, opts[:required])
51
+
52
+ config[:grouped_filters] = {
53
+ names: filter_names,
54
+ required: opts[:required]
55
+ }
56
+ end
57
+
47
58
  def sort_all(&blk)
48
59
  if block_given?
49
60
  config[:_sort_all] = blk
@@ -96,9 +96,14 @@ module Graphiti
96
96
  extra_attributes: extra_attributes(r),
97
97
  sorts: sorts(r),
98
98
  filters: filters(r),
99
- relationships: relationships(r)
99
+ relationships: relationships(r),
100
+ stats: stats(r)
100
101
  }
101
102
 
103
+ if r.grouped_filters.any?
104
+ config[:filter_group] = r.grouped_filters
105
+ end
106
+
102
107
  if r.default_sort
103
108
  default_sort = r.default_sort.map { |s|
104
109
  {s.keys.first.to_s => s.values.first.to_s}
@@ -165,6 +170,14 @@ module Graphiti
165
170
  end
166
171
  end
167
172
 
173
+ def stats(resource)
174
+ {}.tap do |stats|
175
+ resource.stats.each_pair do |name, config|
176
+ stats[name] = config.calculations.keys
177
+ end
178
+ end
179
+ end
180
+
168
181
  def sorts(resource)
169
182
  {}.tap do |s|
170
183
  resource.sorts.each_pair do |name, sort|
@@ -212,6 +225,10 @@ module Graphiti
212
225
  end
213
226
  end
214
227
 
228
+ def filter_group(resource)
229
+ resource.config[:grouped_filters]
230
+ end
231
+
215
232
  def relationships(resource)
216
233
  {}.tap do |r|
217
234
  resource.sideloads.each_pair do |name, config|
@@ -1,8 +1,8 @@
1
1
  module Graphiti
2
2
  class SchemaDiff
3
3
  def initialize(old, new)
4
- @old = old.deep_symbolize_keys
5
- @new = new.deep_symbolize_keys
4
+ @old = JSON.parse(old.to_json).deep_symbolize_keys
5
+ @new = JSON.parse(new.to_json).deep_symbolize_keys
6
6
  @errors = []
7
7
  end
8
8
 
@@ -30,6 +30,8 @@ module Graphiti
30
30
  compare_extra_attributes(r, new_resource)
31
31
  compare_sorts(r, new_resource)
32
32
  compare_filters(r, new_resource)
33
+ compare_filter_group(r, new_resource)
34
+ compare_stats(r, new_resource)
33
35
  compare_relationships(r, new_resource)
34
36
  end
35
37
  end
@@ -133,12 +135,12 @@ module Graphiti
133
135
  end
134
136
 
135
137
  if new_sort[:only] && !old_sort[:only]
136
- @errors << "#{old_resource[:name]}: sort #{name.inspect} now limited to only #{new_sort[:only].inspect}."
138
+ @errors << "#{old_resource[:name]}: sort #{name.inspect} now limited to only #{new_sort[:only].to_sym.inspect}."
137
139
  end
138
140
 
139
141
  if new_sort[:only] && old_sort[:only]
140
142
  if new_sort[:only] != old_sort[:only]
141
- @errors << "#{old_resource[:name]}: sort #{name.inspect} was limited to only #{old_sort[:only].inspect}, now limited to only #{new_sort[:only].inspect}."
143
+ @errors << "#{old_resource[:name]}: sort #{name.inspect} was limited to only #{old_sort[:only].to_sym.inspect}, now limited to only #{new_sort[:only].to_sym.inspect}."
142
144
  end
143
145
  end
144
146
  end
@@ -204,6 +206,44 @@ module Graphiti
204
206
  end
205
207
  end
206
208
 
209
+ def compare_filter_group(old_resource, new_resource)
210
+ if new_resource[:filter_group]
211
+ if old_resource[:filter_group]
212
+ new_names = new_resource[:filter_group][:names]
213
+ old_names = old_resource[:filter_group][:names]
214
+ diff = new_names - old_names
215
+ if !diff.empty? && new_resource[:filter_group][:required] == "all"
216
+ @errors << "#{old_resource[:name]}: all required filter group #{old_names.map(&:to_sym).inspect} added #{"member".pluralize(diff.length)} #{diff.map(&:to_sym).inspect}."
217
+ end
218
+
219
+ old_required = old_resource[:filter_group][:required]
220
+ new_required = new_resource[:filter_group][:required]
221
+ if old_required == "any" && new_required == "all"
222
+ @errors << "#{old_resource[:name]}: filter group #{old_names.map(&:to_sym).inspect} moved from required: :any to required: :all"
223
+ end
224
+ else
225
+ @errors << "#{old_resource[:name]}: filter group #{new_resource[:filter_group][:names].map(&:to_sym).inspect} was added."
226
+ end
227
+ end
228
+ end
229
+
230
+ def compare_stats(old_resource, new_resource)
231
+ return unless old_resource.key?(:stats)
232
+
233
+ old_resource[:stats].each_pair do |name, old_calculations|
234
+ new_calculations = new_resource[:stats][name]
235
+ if new_calculations
236
+ old_calculations.each do |calc|
237
+ unless new_calculations.include?(calc)
238
+ @errors << "#{old_resource[:name]}: calculation #{calc.to_sym.inspect} was removed from stat #{name.inspect}."
239
+ end
240
+ end
241
+ else
242
+ @errors << "#{old_resource[:name]}: stat #{name.inspect} was removed."
243
+ end
244
+ end
245
+ end
246
+
207
247
  def compare_endpoints
208
248
  @old[:endpoints].each_pair do |path, old_endpoint|
209
249
  unless (new_endpoint = @new[:endpoints][path])
@@ -3,6 +3,13 @@ module Graphiti
3
3
  include Scoping::Filterable
4
4
 
5
5
  def apply
6
+ unless @opts[:bypass_required_filters]
7
+ Graphiti::Scoping::FilterGroupValidator.new(
8
+ resource,
9
+ query_hash
10
+ ).raise_unless_filter_group_requirements_met!
11
+ end
12
+
6
13
  if missing_required_filters.any? && !@opts[:bypass_required_filters]
7
14
  raise Errors::RequiredFilter.new(resource, missing_required_filters)
8
15
  end
@@ -0,0 +1,78 @@
1
+ module Graphiti
2
+ class Scoping::FilterGroupValidator
3
+ VALID_REQUIRED_VALUES = %i[all any]
4
+
5
+ def self.raise_unless_filter_group_requirement_valid!(resource, requirement)
6
+ unless VALID_REQUIRED_VALUES.include?(requirement)
7
+ raise Errors::FilterGroupInvalidRequirement.new(
8
+ resource,
9
+ VALID_REQUIRED_VALUES
10
+ )
11
+ end
12
+
13
+ true
14
+ end
15
+
16
+ def initialize(resource, query_hash)
17
+ @resource = resource
18
+ @query_hash = query_hash
19
+ end
20
+
21
+ def raise_unless_filter_group_requirements_met!
22
+ return if grouped_filters.empty?
23
+
24
+ case filter_group_requirement
25
+ when :all
26
+ raise_unless_all_requirements_met!
27
+ when :any
28
+ raise_unless_any_requirements_met!
29
+ end
30
+
31
+ true
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :resource, :query_hash
37
+
38
+ def raise_unless_all_requirements_met!
39
+ met = filter_group_names.all? { |filter_name| filter_group_filter_param.key?(filter_name) }
40
+
41
+ unless met
42
+ raise Errors::FilterGroupMissingRequiredFilters.new(
43
+ resource,
44
+ filter_group_names,
45
+ filter_group_requirement
46
+ )
47
+ end
48
+ end
49
+
50
+ def raise_unless_any_requirements_met!
51
+ met = filter_group_names.any? { |filter_name| filter_group_filter_param.key?(filter_name) }
52
+
53
+ unless met
54
+ raise Errors::FilterGroupMissingRequiredFilters.new(
55
+ resource,
56
+ filter_group_names,
57
+ filter_group_requirement
58
+ )
59
+ end
60
+ end
61
+
62
+ def filter_group_names
63
+ grouped_filters.fetch(:names, [])
64
+ end
65
+
66
+ def filter_group_requirement
67
+ grouped_filters.fetch(:required, :invalid)
68
+ end
69
+
70
+ def grouped_filters
71
+ resource.grouped_filters
72
+ end
73
+
74
+ def filter_group_filter_param
75
+ query_hash.fetch(:filter, {})
76
+ end
77
+ end
78
+ end
@@ -136,6 +136,14 @@ module Graphiti
136
136
  base_filter(parents)
137
137
  end
138
138
 
139
+ def link_extra_fields
140
+ extra_fields_name = [association_name, resource.type].find { |param|
141
+ context.params.dig(:extra_fields, param)
142
+ }
143
+
144
+ {resource.type => context.params.dig(:extra_fields, extra_fields_name)} if extra_fields_name
145
+ end
146
+
139
147
  # The parent resource is a remote,
140
148
  # AND the sideload is a remote to the same endpoint
141
149
  def shared_remote?
@@ -63,6 +63,10 @@ module Graphiti
63
63
  params[:filter] = @sideload.link_filter([@model])
64
64
  end
65
65
 
66
+ if (extra_fields = @sideload.link_extra_fields)
67
+ params[:extra_fields] ||= extra_fields
68
+ end
69
+
66
70
  @sideload.params_proc&.call(params, [@model], context)
67
71
  end
68
72
  end
@@ -16,13 +16,11 @@ module Graphiti
16
16
 
17
17
  if @name == :id
18
18
  @serializer.id(&proc)
19
- elsif @attr[:proc]
19
+ elsif @attr[:proc] ||
20
+ !previously_applied? ||
21
+ previously_applied_via_resource?
20
22
  @serializer.send(_method, @name, serializer_options, &proc)
21
- elsif @serializer.attribute_blocks[@name].nil?
22
- @serializer.send(_method, @name, serializer_options, &proc)
23
- elsif @serializer.send(applied_method).include?(@name)
24
- @serializer.field_condition_blocks[@name] = guard if guard?
25
- else
23
+ else # Previously applied via explicit serializer, so wrap it
26
24
  inner = @serializer.attribute_blocks.delete(@name)
27
25
  wrapped = wrap_proc(inner)
28
26
  @serializer.send(_method, @name, serializer_options, &wrapped)
@@ -34,6 +32,14 @@ module Graphiti
34
32
 
35
33
  private
36
34
 
35
+ def previously_applied?
36
+ @serializer.attribute_blocks[@name].present?
37
+ end
38
+
39
+ def previously_applied_via_resource?
40
+ @serializer.send(applied_method).include?(@name)
41
+ end
42
+
37
43
  def previously_guarded?
38
44
  @serializer.field_condition_blocks[@name]
39
45
  end
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.2.33"
2
+ VERSION = "1.2.38"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.33
4
+ version: 1.2.38
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-07 00:00:00.000000000 Z
11
+ date: 2021-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializable
@@ -308,6 +308,7 @@ files:
308
308
  - lib/graphiti/scoping/default_filter.rb
309
309
  - lib/graphiti/scoping/extra_attributes.rb
310
310
  - lib/graphiti/scoping/filter.rb
311
+ - lib/graphiti/scoping/filter_group_validator.rb
311
312
  - lib/graphiti/scoping/filterable.rb
312
313
  - lib/graphiti/scoping/paginate.rb
313
314
  - lib/graphiti/scoping/sort.rb