graphiti 1.0.alpha.8 → 1.0.alpha.9

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
  SHA1:
3
- metadata.gz: 68063fcedb7deeeb604ebb6c88e58fe525867ee8
4
- data.tar.gz: cb720f2dcada7d2b92332e8b734ff9fcdc879c01
3
+ metadata.gz: caacb7b46b4b6380d1202102d599ea89292883e4
4
+ data.tar.gz: e1817089ad066f588c1b7b4d43e595e923bd93fc
5
5
  SHA512:
6
- metadata.gz: 9bba382cda2e598726ab189f9b38e256fce145cbe911c9342d706811930da89ae76947f011ff86dacb3e7265b1da305e7074e9471eb7e7d9bbf45ee1ac24484f
7
- data.tar.gz: aa1ab4574c5bb2a51fb0c76d251c0061e44af47287e7906121d566697fa9eb8a8f4cddf65d9dfe7bd2c33f402761a9b2911f4ac37925175cfe55233535f4b1bb
6
+ metadata.gz: c77ef723750a6220eafec17a1312886c75cb20e60fda62ea1a648916c8ddedf4f670abd0be7b7a62806755036aa674f78bb8ec98e587df2c8304e6c89c678d33
7
+ data.tar.gz: 97aa9cf93a8af91459b402b2379c81a461330fd47a467c5eb79a4a768569a6c15ba4ad2238f08bb41d7563607918d7b30c2dfdc2be5197e8d97cfaf77a3f6fe0
data/lib/graphiti.rb CHANGED
@@ -62,12 +62,11 @@ require "graphiti/util/serializer_attributes"
62
62
  require "graphiti/util/serializer_relationships"
63
63
  require "graphiti/util/class"
64
64
  require "graphiti/util/link"
65
-
66
65
  require 'graphiti/adapters/null'
67
-
68
66
  require "graphiti/extensions/extra_attribute"
69
67
  require "graphiti/extensions/boolean_attribute"
70
68
  require "graphiti/extensions/temp_id"
69
+ require "graphiti/serializer"
71
70
 
72
71
  if defined?(ActiveRecord)
73
72
  require 'graphiti/adapters/active_record'
@@ -3,14 +3,14 @@ module Graphiti
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module Overrides
6
- def sideload_whitelist=(val)
6
+ def sideload_allowlist=(val)
7
7
  super(JSONAPI::IncludeDirective.new(val).to_hash)
8
8
  end
9
9
  end
10
10
 
11
11
  included do
12
- class_attribute :sideload_whitelist
13
- self.sideload_whitelist = {}
12
+ class_attribute :sideload_allowlist
13
+ self.sideload_allowlist = {}
14
14
  class << self;prepend Overrides;end
15
15
  end
16
16
  end
@@ -36,16 +36,16 @@ Remove the single: true option to bypass this error.
36
36
  end
37
37
 
38
38
  class UnsupportedSort < Base
39
- def initialize(resource, attribute, whitelist, direction)
39
+ def initialize(resource, attribute, allowlist, direction)
40
40
  @resource = resource
41
41
  @attribute = attribute
42
- @whitelist = whitelist
42
+ @allowlist = allowlist
43
43
  @direction = direction
44
44
  end
45
45
 
46
46
  def message
47
47
  <<-MSG
48
- #{@resource.class.name}: tried to sort on attribute #{@attribute.inspect}, but passed #{@direction.inspect} when only #{@whitelist.inspect} is supported.
48
+ #{@resource.class.name}: tried to sort on attribute #{@attribute.inspect}, but passed #{@direction.inspect} when only #{@allowlist.inspect} is supported.
49
49
  MSG
50
50
  end
51
51
  end
@@ -72,12 +72,12 @@ Remove the single: true option to bypass this error.
72
72
 
73
73
  def message
74
74
  allow = @filter.values[0][:allow]
75
- reject = @filter.values[0][:reject]
75
+ deny = @filter.values[0][:deny]
76
76
  msg = <<-MSG
77
77
  #{@resource.class.name}: tried to filter on #{@filter.keys[0].inspect}, but passed invalid value #{@value.inspect}.
78
78
  MSG
79
- msg << "\nWhitelist: #{allow.inspect}" if allow
80
- msg << "\nBlacklist: #{reject.inspect}" if reject
79
+ msg << "\nAllowlist: #{allow.inspect}" if allow
80
+ msg << "\nDenylist: #{deny.inspect}" if deny
81
81
  msg
82
82
  end
83
83
  end
@@ -192,6 +192,19 @@ Graphiti.config.context_for_endpoint = ->(path, action) { ... }
192
192
  end
193
193
  end
194
194
 
195
+ class InvalidJSONArray < Base
196
+ def initialize(resource, value)
197
+ @resource = resource
198
+ @value = value
199
+ end
200
+
201
+ def message
202
+ <<-MSG
203
+ #{@resource.class.name}: passed filter with value #{@value.inspect}, and failed attempting to parse as JSON array.
204
+ MSG
205
+ end
206
+ end
207
+
195
208
  class InvalidEndpoint < Base
196
209
  def initialize(resource_class, path, action)
197
210
  @resource_class = resource_class
@@ -27,7 +27,3 @@ module Graphiti
27
27
  end
28
28
  end
29
29
  end
30
-
31
- JSONAPI::Serializable::Resource.class_eval do
32
- include Graphiti::Extensions::BooleanAttribute
33
- end
@@ -24,7 +24,7 @@ module Graphiti
24
24
  # end
25
25
  # end
26
26
  #
27
- # class SerializablePerson < JSONAPI::Serializable::Resource
27
+ # class SerializablePerson < Graphiti::Serializer
28
28
  # # ... code ...
29
29
  # extra_attribute :net_worth do
30
30
  # @object.assets.sum(&:value)
@@ -57,14 +57,3 @@ module Graphiti
57
57
  end
58
58
  end
59
59
  end
60
-
61
- JSONAPI::Serializable::Resource.class_eval do
62
- def self.inherited(klass)
63
- super
64
- klass.class_eval do
65
- extend JSONAPI::Serializable::Resource::ConditionalFields
66
- end
67
- end
68
-
69
- include Graphiti::Extensions::ExtraAttribute
70
- end
@@ -20,7 +20,3 @@ module Graphiti
20
20
  end
21
21
  end
22
22
  end
23
-
24
- JSONAPI::Serializable::Resource.class_eval do
25
- prepend Graphiti::SerializableTempId
26
- end
@@ -25,7 +25,6 @@ module Graphiti
25
25
  end
26
26
  end
27
27
  end
28
- JSONAPI::Serializable::Resource.send(:include, SerializableHash)
29
28
 
30
29
  class HashRenderer
31
30
  def initialize(resource)
@@ -156,13 +156,13 @@ module Graphiti
156
156
  @include_hash ||= begin
157
157
  requested = include_directive.to_hash
158
158
 
159
- whitelist = nil
160
- if @resource.context && @resource.context.respond_to?(:sideload_whitelist)
161
- whitelist = @resource.context.sideload_whitelist
162
- whitelist = whitelist[@resource.context_namespace] if whitelist
159
+ allowlist = nil
160
+ if @resource.context && @resource.context.respond_to?(:sideload_allowlist)
161
+ allowlist = @resource.context.sideload_allowlist
162
+ allowlist = allowlist[@resource.context_namespace] if allowlist
163
163
  end
164
164
 
165
- whitelist ? Util::IncludeParams.scrub(requested, whitelist) : requested
165
+ allowlist ? Util::IncludeParams.scrub(requested, allowlist) : requested
166
166
  end
167
167
 
168
168
  @include_hash
@@ -62,7 +62,11 @@ module Graphiti
62
62
 
63
63
  def typecast(name, value, flag)
64
64
  att = get_attr!(name, flag, request: true)
65
- type = Graphiti::Types[att[:type]]
65
+ type_name = att[:type]
66
+ if flag == :filterable
67
+ type_name = filters[name][:type]
68
+ end
69
+ type = Graphiti::Types[type_name]
66
70
  return if value.nil? && type[:kind] != 'array'
67
71
  begin
68
72
  flag = :read if flag == :readable
@@ -134,7 +134,7 @@ module Graphiti
134
134
 
135
135
  # @api private
136
136
  def infer_serializer_superclass
137
- serializer_class = JSONAPI::Serializable::Resource
137
+ serializer_class = ::Graphiti::Serializer
138
138
  namespace = Util::Class.namespace_for(self)
139
139
  app_serializer = "#{namespace}::ApplicationSerializer"
140
140
  .safe_constantize
@@ -185,10 +185,6 @@ module Graphiti
185
185
  attributes.merge(extra_attributes)
186
186
  end
187
187
 
188
- def attribute_cache
189
- @attribute_cache ||= all_attributes
190
- end
191
-
192
188
  def sideloads
193
189
  config[:sideloads]
194
190
  end
@@ -6,6 +6,7 @@ module Graphiti
6
6
  class_methods do
7
7
  def filter(name, *args, &blk)
8
8
  opts = args.extract_options!
9
+ type_override = args[0]
9
10
 
10
11
  if att = get_attr(name, :filterable, raise_error: :only_unsupported)
11
12
  aliases = [name, opts[:aliases]].flatten.compact
@@ -19,9 +20,9 @@ module Graphiti
19
20
  config[:filters][name.to_sym] = {
20
21
  aliases: aliases,
21
22
  name: name.to_sym,
22
- type: att[:type],
23
+ type: type_override || att[:type],
23
24
  allow: opts[:allow],
24
- reject: opts[:reject],
25
+ deny: opts[:deny],
25
26
  single: !!opts[:single],
26
27
  dependencies: opts[:dependent],
27
28
  required: required,
@@ -57,9 +57,9 @@ module Graphiti
57
57
  end
58
58
 
59
59
  actions[a] = { resource: r.name }
60
- if whitelist = ctx.sideload_whitelist
61
- if whitelist[a]
62
- actions[a].merge!(sideload_whitelist: whitelist[a])
60
+ if allowlist = ctx.sideload_allowlist
61
+ if allowlist[a]
62
+ actions[a].merge!(sideload_allowlist: allowlist[a])
63
63
  end
64
64
  end
65
65
  end
@@ -165,7 +165,7 @@ module Graphiti
165
165
 
166
166
  config[:single] = true if filter[:single]
167
167
  config[:allow] = filter[:allow].map(&:to_s) if filter[:allow]
168
- config[:reject] = filter[:reject].map(&:to_s) if filter[:reject]
168
+ config[:deny] = filter[:deny].map(&:to_s) if filter[:deny]
169
169
  config[:dependencies] = filter[:dependencies].map(&:to_s) if filter[:dependencies]
170
170
 
171
171
  attr = resource.attributes[name]
@@ -173,16 +173,16 @@ module Graphiti
173
173
  old = old_filter[:allow] || []
174
174
  diff = new - old
175
175
  if diff.length > 0
176
- @errors << "#{old_resource[:name]}: filter #{name.inspect} whitelist went from #{old.inspect} to #{new.inspect}."
176
+ @errors << "#{old_resource[:name]}: filter #{name.inspect} allowlist went from #{old.inspect} to #{new.inspect}."
177
177
  end
178
178
  end
179
179
 
180
- if new_filter[:reject] != old_filter[:reject]
181
- new = new_filter[:reject] || []
182
- old = old_filter[:reject] || []
180
+ if new_filter[:deny] != old_filter[:deny]
181
+ new = new_filter[:deny] || []
182
+ old = old_filter[:deny] || []
183
183
  diff = new - old
184
184
  if diff.length > 0
185
- @errors << "#{old_resource[:name]}: filter #{name.inspect} blacklist went from #{old.inspect} to #{new.inspect}."
185
+ @errors << "#{old_resource[:name]}: filter #{name.inspect} denylist went from #{old.inspect} to #{new.inspect}."
186
186
  end
187
187
  end
188
188
 
@@ -215,16 +215,16 @@ module Graphiti
215
215
  next
216
216
  end
217
217
 
218
- if new_action[:sideload_whitelist] && !old_action[:sideload_whitelist]
219
- @errors << "Endpoint \"#{path}\" added sideload whitelist."
218
+ if new_action[:sideload_allowlist] && !old_action[:sideload_allowlist]
219
+ @errors << "Endpoint \"#{path}\" added sideload allowlist."
220
220
  end
221
221
 
222
- if new_action[:sideload_whitelist]
223
- if new_action[:sideload_whitelist] != old_action[:sideload_whitelist]
222
+ if new_action[:sideload_allowlist]
223
+ if new_action[:sideload_allowlist] != old_action[:sideload_allowlist]
224
224
  removal = Util::Hash.include_removed? \
225
- new_action[:sideload_whitelist], old_action[:sideload_whitelist]
225
+ new_action[:sideload_allowlist], old_action[:sideload_allowlist]
226
226
  if removal
227
- @errors << "Endpoint \"#{path}\" had incompatible sideload whitelist. Was #{old_action[:sideload_whitelist].inspect}, now #{new_action[:sideload_whitelist].inspect}."
227
+ @errors << "Endpoint \"#{path}\" had incompatible sideload allowlist. Was #{old_action[:sideload_allowlist].inspect}, now #{new_action[:sideload_allowlist].inspect}."
228
228
  end
229
229
  end
230
230
  end
@@ -1,12 +1,12 @@
1
1
  module Graphiti
2
2
  # Apply filtering logic to the scope
3
3
  #
4
- # If the user requests to filter a field that has not been whitelisted,
4
+ # If the user requests to filter a field that has not been allowlisted,
5
5
  # a +Graphiti::Errors::BadFilter+ error will be raised.
6
6
  #
7
- # allow_filter :title # :title now whitelisted
7
+ # allow_filter :title # :title now allowlisted
8
8
  #
9
- # If the user requests a filter field that has been whitelisted, but
9
+ # If the user requests a filter field that has been allowlisted, but
10
10
  # does not pass the associated `+:if+ clause, +BadFilter+ will be raised.
11
11
  #
12
12
  # allow_filter :title, if: :admin?
@@ -72,39 +72,19 @@ module Graphiti
72
72
  def each_filter
73
73
  filter_param.each_pair do |param_name, param_value|
74
74
  filter = find_filter!(param_name)
75
- param_value = { eq: param_value } unless param_value.is_a?(Hash)
76
- value = param_value.values.first
77
- operator = param_value.keys.first
78
- value = param_value.values.first unless filter.values[0][:type] == :hash
79
- value = value.split(',') if value.is_a?(String) && value.include?(',')
80
-
81
- if filter.values[0][:single] && value.is_a?(Array)
82
- raise Errors::SingularFilter.new(resource, filter, value)
83
- end
84
-
85
- value = coerce_types(param_name.to_sym, value)
86
-
87
- value.each do |v|
88
- if allow = filter.values[0][:allow]
89
- unless allow.include?(v)
90
- raise Errors::InvalidFilterValue.new(resource, filter, value)
91
- end
92
- end
93
-
94
- if reject = filter.values[0][:reject]
95
- if reject.include?(v)
96
- raise Errors::InvalidFilterValue.new(resource, filter, value)
97
- end
98
- end
99
- end
100
-
75
+ value, operator = normalize_param(filter, param_value)
76
+ value = parse_string_value(filter.values[0], value) if value.is_a?(String)
77
+ validate_singular(resource, filter, value)
78
+ value = coerce_types(filter.values[0], param_name.to_sym, value)
79
+ validate_allowlist(resource, filter, value)
80
+ validate_denylist(resource, filter, value)
101
81
  value = value[0] if filter.values[0][:single]
102
82
  yield filter, operator, value
103
83
  end
104
84
  end
105
85
 
106
- def coerce_types(name, value)
107
- type_name = @resource.all_attributes[name][:type]
86
+ def coerce_types(filter, name, value)
87
+ type_name = filter[:type]
108
88
  is_array = type_name.to_s.starts_with?('array_of') ||
109
89
  Types[type_name][:canonical_name] == :array
110
90
 
@@ -115,5 +95,98 @@ module Graphiti
115
95
  value.map { |v| @resource.typecast(name, v, :filterable) }
116
96
  end
117
97
  end
98
+
99
+ def normalize_param(filter, param_value)
100
+ param_value = { eq: param_value } unless param_value.is_a?(Hash)
101
+ value = param_value.values.first
102
+ operator = param_value.keys.first
103
+
104
+ if filter.values[0][:type] == :hash
105
+ value, operator = \
106
+ parse_hash_value(filter, param_value, value, operator)
107
+ else
108
+ value = param_value.values.first
109
+ end
110
+
111
+ [value, operator]
112
+ end
113
+
114
+ def validate_singular(resource, filter, value)
115
+ if filter.values[0][:single] && value.is_a?(Array)
116
+ raise Errors::SingularFilter.new(resource, filter, value)
117
+ end
118
+ end
119
+
120
+ def validate_allowlist(resource, filter, values)
121
+ values.each do |v|
122
+ if allow = filter.values[0][:allow]
123
+ unless allow.include?(v)
124
+ raise Errors::InvalidFilterValue.new(resource, filter, v)
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def validate_denylist(resource, filter, values)
131
+ values.each do |v|
132
+ if deny = filter.values[0][:deny]
133
+ if deny.include?(v)
134
+ raise Errors::InvalidFilterValue.new(resource, filter, v)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ def parse_hash_value(filter, param_value, value, operator)
141
+ if operator != :eq
142
+ operator = :eq
143
+ value = param_value
144
+ end
145
+
146
+ if value.is_a?(String)
147
+ value = value.gsub('{{{', '{').gsub('}}}', '}')
148
+ end
149
+
150
+ [value, operator]
151
+ end
152
+
153
+ # foo,bar,baz becomes ["foo", "bar", "baz"] (unless array type)
154
+ # {{foo}} becomes ["foo"]
155
+ # {{foo,bar}},baz becomes ["foo,bar", "baz"]
156
+ #
157
+ # JSON of
158
+ # {{{ "id": 1 }}} becomes { 'id' => 1 }
159
+ def parse_string_value(filter, value)
160
+ type = Graphiti::Types[filter[:type]]
161
+ array_or_string = [:string, :array].include?(type[:canonical_name])
162
+ if (arr = value.scan(/\[.*?\]/)).present? && array_or_string
163
+ value = arr.map do |json|
164
+ begin
165
+ JSON.parse(json)
166
+ rescue
167
+ raise Errors::InvalidJSONArray.new(resource, value)
168
+ end
169
+ end
170
+ value = value[0] if value.length == 1
171
+ else
172
+ value = parse_string_arrays(value)
173
+ end
174
+ value
175
+ end
176
+
177
+ def parse_string_arrays(value)
178
+ # Find the quoted strings
179
+ quotes = value.scan(/{{.*?}}/)
180
+ # remove them from the rest
181
+ quotes.each { |q| value.gsub!(q, '') }
182
+ # remove the quote characters from the quoted strings
183
+ quotes.each { |q| q.gsub!('{{', '').gsub!('}}', '') }
184
+ # merge everything back together into an array
185
+ value = Array(value.split(',')) + quotes
186
+ # remove any blanks that are left
187
+ value.reject! { |v| v.length.zero? }
188
+ value = value[0] if value.length == 1
189
+ value
190
+ end
118
191
  end
119
192
  end
@@ -0,0 +1,30 @@
1
+ module Graphiti
2
+ class Serializer < JSONAPI::Serializable::Resource
3
+ include Graphiti::Extensions::BooleanAttribute
4
+ include Graphiti::Extensions::ExtraAttribute
5
+ include Graphiti::SerializableHash
6
+ prepend Graphiti::SerializableTempId
7
+
8
+ def self.inherited(klass)
9
+ super
10
+ klass.class_eval do
11
+ extend JSONAPI::Serializable::Resource::ConditionalFields
12
+ end
13
+ end
14
+
15
+ # Temporary fix until fixed upstream
16
+ # https://github.com/jsonapi-rb/jsonapi-serializable/pull/102
17
+ def requested_relationships(fields)
18
+ @_relationships
19
+ end
20
+
21
+ # Allow access to resource methods
22
+ def method_missing(id, *args, &blk)
23
+ if @resource.respond_to?(id, true)
24
+ @resource.send(id, *args, &blk)
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
@@ -69,7 +69,8 @@ module Graphiti
69
69
  end
70
70
 
71
71
  PresentParamsHash = create(::Hash) do |input|
72
- Dry::Types['params.hash'][input].deep_symbolize_keys
72
+ input = JSON.parse(input) if input.is_a?(String)
73
+ Dry::Types['params.hash'][input]
73
74
  end
74
75
 
75
76
  REQUIRED_KEYS = [:params, :read, :write, :kind, :description]
@@ -56,10 +56,6 @@ module Graphiti
56
56
  attribute[flag] != :required
57
57
  end
58
58
 
59
- def cache?
60
- !!@cache
61
- end
62
-
63
59
  def error_class
64
60
  Errors::AttributeError
65
61
  end
@@ -68,15 +64,8 @@ module Graphiti
68
64
  attribute[flag] != false
69
65
  end
70
66
 
71
- # If running in a request context, we've already loaded everything
72
- # and there's no reason to perform logic, so go through attriubte cache
73
- # Otherwise, this is a performance hit during typecasting
74
67
  def attribute
75
- @attribute ||= if request?
76
- resource.class.attribute_cache[name]
77
- else
78
- resource.all_attributes[name]
79
- end
68
+ @attribute ||= resource.all_attributes[name]
80
69
  end
81
70
 
82
71
  def attribute?
@@ -9,7 +9,7 @@ module Graphiti
9
9
  #
10
10
  # But our resource had this code:
11
11
  #
12
- # sideload_whitelist({ index: [:comments] })
12
+ # sideload_allowlist({ index: [:comments] })
13
13
  #
14
14
  # We should drop the 'author' sideload from the request.
15
15
  #
@@ -57,20 +57,34 @@ module Graphiti
57
57
  end
58
58
  end
59
59
 
60
+ def typecast(type)
61
+ _resource = @resource
62
+ _name = @name
63
+ _type = type
64
+ ->(value) {
65
+ begin
66
+ _type[value] unless value.nil?
67
+ rescue Exception => e
68
+ raise Errors::TypecastFailed.new(_resource, _name, value, e)
69
+ end
70
+ }
71
+ end
72
+
60
73
  def default_proc
61
74
  _name = @name
62
75
  _resource = @resource.new
76
+ _typecast = typecast(Graphiti::Types[@attr[:type]][:read])
63
77
  ->(_) {
64
- _resource.typecast(_name, @object.send(_name), :readable)
78
+ _typecast.call(@object.send(_name))
65
79
  }
66
80
  end
67
81
 
68
82
  def wrap_proc(inner)
69
83
  _resource = @resource.new
70
84
  _name = @name
85
+ _typecast = typecast(Graphiti::Types[@attr[:type]][:read])
71
86
  ->(serializer_instance = nil) {
72
- value = serializer_instance.instance_eval(&inner)
73
- _resource.typecast(_name, value, :readable)
87
+ _typecast.call(serializer_instance.instance_eval(&inner))
74
88
  }
75
89
  end
76
90
 
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.0.alpha.8"
2
+ VERSION = "1.0.alpha.9"
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.0.alpha.8
4
+ version: 1.0.alpha.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-08-22 00:00:00.000000000 Z
11
+ date: 2018-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializable
@@ -278,6 +278,7 @@ files:
278
278
  - lib/graphiti/scoping/filterable.rb
279
279
  - lib/graphiti/scoping/paginate.rb
280
280
  - lib/graphiti/scoping/sort.rb
281
+ - lib/graphiti/serializer.rb
281
282
  - lib/graphiti/sideload.rb
282
283
  - lib/graphiti/sideload/belongs_to.rb
283
284
  - lib/graphiti/sideload/has_many.rb