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

Sign up to get free protection for your applications and to get access to all the features.
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