sinja 1.2.2 → 1.2.3
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 +4 -4
- data/README.md +49 -21
- data/demo-app/app.rb +8 -7
- data/demo-app/classes/author.rb +1 -1
- data/demo-app/classes/comment.rb +1 -1
- data/demo-app/classes/post.rb +1 -1
- data/lib/sinja.rb +33 -30
- data/lib/sinja/config.rb +18 -16
- data/lib/sinja/errors.rb +2 -2
- data/lib/sinja/helpers/serializers.rb +31 -25
- data/lib/sinja/resource.rb +5 -5
- data/lib/sinja/resource_routes.rb +4 -4
- data/lib/sinja/version.rb +1 -1
- data/sinja.gemspec +9 -8
- metadata +11 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4606d9daabb3be7a7aca0c603f36134f86cf2885
|
4
|
+
data.tar.gz: 210cc332302ab5b34140b14bd74e24c2a72fb93a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5627d6ea47b5a62945a49b42e69f165ca10b100723b17cd3efd9b04fdc1e8b67b84e334a98b8814b90961c38958287fb1c86fce5389f9250c72cf58c234d961
|
7
|
+
data.tar.gz: f1dda55b7f38c2e09f7ccb4d00fce2a09ef2342df085cd847e2937d58ccf5c5acfae2d9d004594bc12174ddf0abf0b8129e2823cbdc1124793395708e9850299
|
data/README.md
CHANGED
@@ -20,13 +20,14 @@ DSL to enable resource-, relationship-, and role-centric API development, and
|
|
20
20
|
it configures Sinatra with the proper settings, MIME-types, filters,
|
21
21
|
conditions, and error-handling.
|
22
22
|
|
23
|
-
There are [many][31] parsing (deserializing)
|
24
|
-
|
25
|
-
|
23
|
+
There are [many][31] parsing (deserializing), rendering (serializing), and
|
24
|
+
other "JSON API" libraries available for Ruby, but relatively few that attempt
|
25
|
+
to correctly implement the entire {json:api} server specification, including
|
26
26
|
routing, request header and query parameter checking, and relationship
|
27
|
-
side-loading.
|
27
|
+
side-loading. Sinja lets you focus on the business logic of your applications
|
28
28
|
without worrying about the specification, and without pulling in a heavy
|
29
|
-
framework like [Rails][16]. It's lightweight
|
29
|
+
framework like [Rails][16]. It's lightweight, ORM-agnostic, and
|
30
|
+
[Ember.js][32]-friendly!
|
30
31
|
|
31
32
|
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
32
33
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
@@ -224,7 +225,7 @@ class App < Sinatra::Base
|
|
224
225
|
show do
|
225
226
|
headers 'X-ISBN'=>resource.isbn
|
226
227
|
last_modified resource.updated_at
|
227
|
-
next resource, include:
|
228
|
+
next resource, include: ['author']
|
228
229
|
end
|
229
230
|
|
230
231
|
has_one :author do
|
@@ -318,12 +319,12 @@ configure_jsonapi do |c|
|
|
318
319
|
|
319
320
|
# You can't set this directly; see "Query Parameters" below
|
320
321
|
#c.query_params = {
|
321
|
-
# :include=>
|
322
|
+
# :include=>Array, :fields=>Hash, :filter=>Hash, :page=>Hash, :sort=>Array
|
322
323
|
#}
|
323
324
|
|
324
325
|
#c.page_using = {} # see "Paging" below
|
325
326
|
|
326
|
-
# Set the error logger used by Sinja
|
327
|
+
# Set the error logger used by Sinja (set to `nil' to disable)
|
327
328
|
#c.error_logger = ->(error_hash) { logger.error('sinja') { error_hash } }
|
328
329
|
|
329
330
|
# A hash of options to pass to JSONAPI::Serializer.serialize
|
@@ -394,6 +395,40 @@ simply returns `resource`.
|
|
394
395
|
|
395
396
|
### Action Helpers
|
396
397
|
|
398
|
+
<table>
|
399
|
+
<thead>
|
400
|
+
<tr>
|
401
|
+
<th><a href="#resource"><code>resource</code></a></th>
|
402
|
+
<th><a href="#has_one"><code>has_one</code></a></th>
|
403
|
+
<th><a href="#has_many"><code>has_many</code></a></th>
|
404
|
+
</tr>
|
405
|
+
</thead>
|
406
|
+
<tbody>
|
407
|
+
<tr valign="top">
|
408
|
+
<td><ul>
|
409
|
+
<li><a href="#index---array"><code>index</code></a></li>
|
410
|
+
<li><code>show</code> <a href="#show---object">w/ resource locator</a> or <a href="#show-id---object">w/o</a></li>
|
411
|
+
<li><a href="#show_many-ids---array"><code>show_many</code></a></li>
|
412
|
+
<li><code>create</code> <a href="#create-attr-id---id-object">w/ client-generated IDs</a> or <a href="#create-attr---id-object">w/o</a></li>
|
413
|
+
<li><a href="#update-attr---object"><code>update</code></a></li>
|
414
|
+
<li><a href="#destroy-"><code>destroy</code></a></li>
|
415
|
+
</ul></td>
|
416
|
+
<td><ul>
|
417
|
+
<li><a href="#pluck---object"><code>pluck</code></a></li>
|
418
|
+
<li><a href="#prune---trueclass"><code>prune</code></a></li>
|
419
|
+
<li><a href="#graft-rio---trueclass"><code>graft</code></a></li>
|
420
|
+
</ul></td>
|
421
|
+
<td><ul>
|
422
|
+
<li><a href="#fetch---array"><code>fetch</code></a></li>
|
423
|
+
<li><a href="#clear---trueclass"><code>clear</code></a></li>
|
424
|
+
<li><a href="#replace-rios---trueclass"><code>replace</code></a></li>
|
425
|
+
<li><a href="#merge-rios---trueclass"><code>merge</code></a></li>
|
426
|
+
<li><a href="#subtract-rios---trueclass"><code>subtract</code></a></li>
|
427
|
+
</ul></td>
|
428
|
+
</tr>
|
429
|
+
</tbody>
|
430
|
+
</table>
|
431
|
+
|
397
432
|
Action helpers should be defined within the appropriate block contexts
|
398
433
|
(`resource`, `has_one`, or `has_many`) using the given keywords and arguments
|
399
434
|
below. Implicitly return the expected values as described below (as an array if
|
@@ -730,10 +765,8 @@ end
|
|
730
765
|
resource :foos do
|
731
766
|
helpers do
|
732
767
|
def role
|
733
|
-
|
734
|
-
|
735
|
-
else
|
736
|
-
super
|
768
|
+
super.tap do |a|
|
769
|
+
a << :owner if resource&.owner == logged_in_user
|
737
770
|
end
|
738
771
|
end
|
739
772
|
end
|
@@ -796,12 +829,6 @@ resource :posts do
|
|
796
829
|
index(filter_by: [:title, :type]) do
|
797
830
|
Foo # return a Sequel::Dataset (instead of an array of Sequel::Model instances)
|
798
831
|
end
|
799
|
-
|
800
|
-
has_many :comments do
|
801
|
-
fetch(filter_by: :status) do
|
802
|
-
resource.comments_dataset # return a Sequel::Dataset
|
803
|
-
end
|
804
|
-
end
|
805
832
|
end
|
806
833
|
```
|
807
834
|
|
@@ -1411,8 +1438,8 @@ Sinja applications might grow overly large with a block for each resource. I am
|
|
1411
1438
|
still working on a better way to handle this (as well as a way to provide
|
1412
1439
|
standalone resource controllers for e.g. cloud functions), but for the time
|
1413
1440
|
being you can store each resource block as its own Proc, and pass it to the
|
1414
|
-
`resource` keyword
|
1415
|
-
|
1441
|
+
`resource` keyword as a block. The migration to some future solution should be
|
1442
|
+
relatively painless. For example:
|
1416
1443
|
|
1417
1444
|
```ruby
|
1418
1445
|
# controllers/foo_controller.rb
|
@@ -1437,7 +1464,7 @@ require_relative 'controllers/foo_controller'
|
|
1437
1464
|
class App < Sinatra::Base
|
1438
1465
|
register Sinatra::JSONAPI
|
1439
1466
|
|
1440
|
-
resource :foos, FooController
|
1467
|
+
resource :foos, &FooController
|
1441
1468
|
|
1442
1469
|
freeze_jsonapi
|
1443
1470
|
end
|
@@ -1544,3 +1571,4 @@ License](http://opensource.org/licenses/MIT).
|
|
1544
1571
|
[29]: https://github.com/brynary/rack-test
|
1545
1572
|
[30]: https://github.com/mwpastore/sinja-sequel
|
1546
1573
|
[31]: http://jsonapi.org/implementations/#server-libraries-ruby
|
1574
|
+
[32]: http://emberjs.com
|
data/demo-app/app.rb
CHANGED
@@ -21,16 +21,17 @@ helpers Sinja::Sequel::Helpers do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def role
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
return unless current_user
|
25
|
+
|
26
|
+
[:logged_in].tap do |a|
|
27
|
+
a << :superuser if current_user.admin?
|
27
28
|
end
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
31
|
-
resource :authors, AuthorController
|
32
|
-
resource :comments, CommentController
|
33
|
-
resource :posts, PostController
|
34
|
-
resource :tags, TagController
|
32
|
+
resource :authors, &AuthorController
|
33
|
+
resource :comments, &CommentController
|
34
|
+
resource :posts, &PostController
|
35
|
+
resource :tags, &TagController
|
35
36
|
|
36
37
|
freeze_jsonapi
|
data/demo-app/classes/author.rb
CHANGED
data/demo-app/classes/comment.rb
CHANGED
data/demo-app/classes/post.rb
CHANGED
data/lib/sinja.rb
CHANGED
@@ -48,7 +48,7 @@ module Sinja
|
|
48
48
|
index.to_h.all? do |key, subkeys|
|
49
49
|
key = key.to_s
|
50
50
|
|
51
|
-
|
51
|
+
params[key].is_a?(Hash) && params[key].any? && Array(subkeys).all? do |subkey|
|
52
52
|
subkey = subkey.to_s
|
53
53
|
|
54
54
|
# TODO: What if deleting one is successful, but not another?
|
@@ -75,29 +75,29 @@ module Sinja
|
|
75
75
|
# Ignore interal Sinatra query parameters (e.g. :captures) and any
|
76
76
|
# "known" query parameter set to `nil' in the configurable.
|
77
77
|
next if !env['rack.request.query_hash'].key?(key.to_s) ||
|
78
|
-
settings._sinja.query_params.fetch(key,
|
78
|
+
settings._sinja.query_params.fetch(key, BasicObject).nil?
|
79
79
|
|
80
80
|
raise BadRequestError, "`#{key}' query parameter not allowed" \
|
81
81
|
unless allow_params.include?(key)
|
82
82
|
|
83
83
|
next if env['sinja.normalized'] == params.object_id
|
84
84
|
|
85
|
-
if
|
85
|
+
if value.instance_of?(String) && settings._sinja.query_params[key] != String
|
86
86
|
params[key.to_s] = value.split(',')
|
87
|
-
elsif !(settings._sinja.query_params[key]
|
87
|
+
elsif !value.is_a?(settings._sinja.query_params[key])
|
88
88
|
raise BadRequestError, "`#{key}' query parameter malformed"
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
92
|
return true if env['sinja.normalized'] == params.object_id
|
93
93
|
|
94
|
-
settings._sinja.query_params.each do |key,
|
95
|
-
next if
|
94
|
+
settings._sinja.query_params.each do |key, klass|
|
95
|
+
next if klass.nil?
|
96
96
|
|
97
97
|
if respond_to?("normalize_#{key}_params")
|
98
|
-
params[key.to_s] = send("normalize_#{key}_params"
|
98
|
+
params[key.to_s] = send("normalize_#{key}_params")
|
99
99
|
else
|
100
|
-
params[key.to_s] ||=
|
100
|
+
params[key.to_s] ||= klass.new
|
101
101
|
end
|
102
102
|
end
|
103
103
|
|
@@ -118,7 +118,7 @@ module Sinja
|
|
118
118
|
def allow(h={})
|
119
119
|
s = Set.new
|
120
120
|
h.each do |method, actions|
|
121
|
-
s << method if
|
121
|
+
s << method if Array(actions).all?(&method(:respond_to?))
|
122
122
|
end
|
123
123
|
headers 'Allow'=>s.map(&:upcase).join(',')
|
124
124
|
end
|
@@ -128,7 +128,7 @@ module Sinja
|
|
128
128
|
end
|
129
129
|
|
130
130
|
if method_defined?(:bad_request?)
|
131
|
-
# This screws up our error-handling logic in Sinatra 2.0, so
|
131
|
+
# This screws up our error-handling logic in Sinatra 2.0, so override it.
|
132
132
|
# https://github.com/sinatra/sinatra/issues/1211
|
133
133
|
# https://github.com/sinatra/sinatra/pull/1212
|
134
134
|
def bad_request?
|
@@ -138,7 +138,7 @@ module Sinja
|
|
138
138
|
|
139
139
|
def can?(action)
|
140
140
|
roles = settings._resource_config[:resource].fetch(action, {})[:roles]
|
141
|
-
roles.nil? || roles.empty? || roles
|
141
|
+
roles.nil? || roles.empty? || roles.intersect?(memoized_role)
|
142
142
|
end
|
143
143
|
|
144
144
|
def content?
|
@@ -154,8 +154,8 @@ module Sinja
|
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|
157
|
-
def normalize_filter_params
|
158
|
-
return
|
157
|
+
def normalize_filter_params
|
158
|
+
return {} unless params[:filter]&.any?
|
159
159
|
|
160
160
|
raise BadRequestError, "Unsupported `filter' query parameter(s)" \
|
161
161
|
unless respond_to?(:filter)
|
@@ -175,7 +175,7 @@ module Sinja
|
|
175
175
|
raise BadRequestError, "Invalid `filter' query parameter(s)"
|
176
176
|
end
|
177
177
|
|
178
|
-
def normalize_sort_params
|
178
|
+
def normalize_sort_params
|
179
179
|
return {} unless params[:sort]&.any?
|
180
180
|
|
181
181
|
raise BadRequestError, "Unsupported `sort' query parameter(s)" \
|
@@ -197,8 +197,8 @@ module Sinja
|
|
197
197
|
raise BadRequestError, "Invalid `sort' query parameter(s)"
|
198
198
|
end
|
199
199
|
|
200
|
-
def normalize_page_params
|
201
|
-
return
|
200
|
+
def normalize_page_params
|
201
|
+
return {} unless params[:page]&.any?
|
202
202
|
|
203
203
|
raise BadRequestError, "Unsupported `page' query parameter(s)" \
|
204
204
|
unless respond_to?(:page)
|
@@ -244,7 +244,7 @@ module Sinja
|
|
244
244
|
end
|
245
245
|
|
246
246
|
def memoized_role
|
247
|
-
@role ||= role
|
247
|
+
@role ||= Roles[*role]
|
248
248
|
end
|
249
249
|
|
250
250
|
def sideloaded?
|
@@ -256,7 +256,7 @@ module Sinja
|
|
256
256
|
end
|
257
257
|
|
258
258
|
def role?(*roles)
|
259
|
-
Roles[*roles]
|
259
|
+
Roles[*roles].intersect?(memoized_role)
|
260
260
|
end
|
261
261
|
|
262
262
|
def sanity_check!(resource_name, id=nil)
|
@@ -288,34 +288,37 @@ module Sinja
|
|
288
288
|
end
|
289
289
|
|
290
290
|
app.not_found do
|
291
|
-
serialize_errors
|
291
|
+
serialize_errors
|
292
292
|
end
|
293
293
|
|
294
294
|
app.error 400...600 do
|
295
|
-
serialize_errors
|
295
|
+
serialize_errors
|
296
296
|
end
|
297
297
|
|
298
298
|
app.error StandardError do
|
299
299
|
env['sinatra.error'].tap do |e|
|
300
300
|
boom =
|
301
|
-
if settings._sinja.not_found_exceptions.any?
|
302
|
-
NotFoundError.new(e.message) unless NotFoundError
|
303
|
-
elsif settings._sinja.conflict_exceptions.any?
|
304
|
-
ConflictError.new(e.message) unless ConflictError
|
305
|
-
elsif settings._sinja.validation_exceptions.any?
|
306
|
-
UnprocessibleEntityError.new(settings._sinja.validation_formatter.(e)) unless UnprocessibleEntityError
|
301
|
+
if settings._sinja.not_found_exceptions.any?(&e.method(:is_a?))
|
302
|
+
NotFoundError.new(e.message) unless e.instance_of?(NotFoundError)
|
303
|
+
elsif settings._sinja.conflict_exceptions.any?(&e.method(:is_a?))
|
304
|
+
ConflictError.new(e.message) unless e.instance_of?(ConflictError)
|
305
|
+
elsif settings._sinja.validation_exceptions.any?(&e.method(:is_a?))
|
306
|
+
UnprocessibleEntityError.new(settings._sinja.validation_formatter.(e)) unless e.instance_of?(UnprocessibleEntityError)
|
307
307
|
end
|
308
308
|
|
309
309
|
handle_exception!(boom) if boom # re-throw the re-packaged exception
|
310
310
|
end
|
311
311
|
|
312
|
-
serialize_errors
|
312
|
+
serialize_errors
|
313
313
|
end
|
314
314
|
end
|
315
315
|
|
316
316
|
def resource(resource_name, konst=nil, &block)
|
317
317
|
abort "Must supply proc constant or block for `resource'" \
|
318
|
-
unless block = (konst if konst.
|
318
|
+
unless block = (konst if konst.instance_of?(Proc)) || block
|
319
|
+
|
320
|
+
warn "DEPRECATED: Pass a block to `resource'; the ability to pass a Proc " \
|
321
|
+
'will be removed in a future version of Sinja.' if konst.instance_of?(Proc)
|
319
322
|
|
320
323
|
resource_name = resource_name.to_s
|
321
324
|
.pluralize
|
@@ -352,7 +355,7 @@ module Sinja
|
|
352
355
|
end
|
353
356
|
end
|
354
357
|
|
355
|
-
|
358
|
+
alias resources resource
|
356
359
|
|
357
360
|
def sinja
|
358
361
|
if block_given?
|
@@ -362,7 +365,7 @@ module Sinja
|
|
362
365
|
end
|
363
366
|
end
|
364
367
|
|
365
|
-
|
368
|
+
alias configure_jsonapi sinja
|
366
369
|
def freeze_jsonapi
|
367
370
|
_sinja.freeze
|
368
371
|
end
|
data/lib/sinja/config.rb
CHANGED
@@ -21,8 +21,8 @@ module Sinja
|
|
21
21
|
end
|
22
22
|
|
23
23
|
if c.respond_to?(:values)
|
24
|
-
c.
|
25
|
-
if Hash
|
24
|
+
c.each_value do |i|
|
25
|
+
if i.is_a?(Hash)
|
26
26
|
deep_freeze(i)
|
27
27
|
else
|
28
28
|
i.freeze
|
@@ -60,11 +60,11 @@ module Sinja
|
|
60
60
|
|
61
61
|
def initialize
|
62
62
|
@query_params = {
|
63
|
-
:include=>
|
64
|
-
:fields=>
|
65
|
-
:filter=>
|
66
|
-
:page=>
|
67
|
-
:sort=>
|
63
|
+
:include=>Array, # passthru to JAS
|
64
|
+
:fields=>Hash, # passthru to JAS
|
65
|
+
:filter=>Hash,
|
66
|
+
:page=>Hash,
|
67
|
+
:sort=>Array
|
68
68
|
}
|
69
69
|
|
70
70
|
@error_logger = ->(h) { logger.error('sinja') { h } }
|
@@ -93,14 +93,14 @@ module Sinja
|
|
93
93
|
@validation_exceptions = Set.new
|
94
94
|
@validation_formatter = ->{ Array.new }
|
95
95
|
|
96
|
-
@opts =
|
96
|
+
@opts = DEFAULT_OPTS.dup
|
97
97
|
@page_using = Hash.new
|
98
98
|
@serializer_opts = deep_copy(DEFAULT_SERIALIZER_OPTS)
|
99
99
|
end
|
100
100
|
|
101
101
|
def error_logger=(f)
|
102
102
|
fail "Invalid error formatter #{f}" \
|
103
|
-
unless f.respond_to?(:call)
|
103
|
+
unless f.respond_to?(:call) || f.nil?
|
104
104
|
|
105
105
|
fail "Can't modify frozen proc" \
|
106
106
|
if @error_logger.frozen?
|
@@ -109,15 +109,15 @@ module Sinja
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def conflict_exceptions=(e=[])
|
112
|
-
@conflict_exceptions.replace(
|
112
|
+
@conflict_exceptions.replace(Array(e))
|
113
113
|
end
|
114
114
|
|
115
115
|
def not_found_exceptions=(e=[])
|
116
|
-
@not_found_exceptions.replace(
|
116
|
+
@not_found_exceptions.replace(Array(e))
|
117
117
|
end
|
118
118
|
|
119
119
|
def validation_exceptions=(e=[])
|
120
|
-
@validation_exceptions.replace(
|
120
|
+
@validation_exceptions.replace(Array(e))
|
121
121
|
end
|
122
122
|
|
123
123
|
def validation_formatter=(f)
|
@@ -154,7 +154,7 @@ module Sinja
|
|
154
154
|
@default_roles[:has_one].merge!(other)
|
155
155
|
end
|
156
156
|
|
157
|
-
DEFAULT_OPTS.
|
157
|
+
DEFAULT_OPTS.each_key do |k|
|
158
158
|
define_method(k) { @opts[k] }
|
159
159
|
define_method("#{k}=") { |v| @opts[k] = v }
|
160
160
|
end
|
@@ -188,9 +188,11 @@ module Sinja
|
|
188
188
|
end
|
189
189
|
|
190
190
|
class Roles < Set
|
191
|
-
def
|
192
|
-
|
191
|
+
def intersect?(other)
|
192
|
+
super(other.instance_of?(self.class) ? other : self.class[*other])
|
193
193
|
end
|
194
|
+
|
195
|
+
alias === intersect?
|
194
196
|
end
|
195
197
|
|
196
198
|
class RolesConfig
|
@@ -211,7 +213,7 @@ module Sinja
|
|
211
213
|
h.each do |action, roles|
|
212
214
|
abort "Unknown or invalid action helper `#{action}' in configuration" \
|
213
215
|
unless @data.key?(action)
|
214
|
-
@data[action].replace(
|
216
|
+
@data[action].replace(Array(roles))
|
215
217
|
end
|
216
218
|
@data
|
217
219
|
end
|
data/lib/sinja/errors.rb
CHANGED
@@ -74,10 +74,10 @@ module Sinja
|
|
74
74
|
attr_reader :tuples
|
75
75
|
|
76
76
|
def initialize(tuples=[])
|
77
|
-
@tuples =
|
77
|
+
@tuples = Array(tuples)
|
78
78
|
|
79
79
|
fail 'Tuples not properly formatted' \
|
80
|
-
unless @tuples.any? && @tuples.all? { |t| Array
|
80
|
+
unless @tuples.any? && @tuples.all? { |t| t.instance_of?(Array) && t.length.between?(2, 3) }
|
81
81
|
|
82
82
|
super(HTTP_STATUS)
|
83
83
|
end
|
@@ -11,7 +11,7 @@ module Sinja
|
|
11
11
|
VALID_PAGINATION_KEYS = Set.new(%i[self first prev next last]).freeze
|
12
12
|
|
13
13
|
def dedasherize(s=nil)
|
14
|
-
s.to_s.underscore.send(Symbol
|
14
|
+
s.to_s.underscore.send(s.instance_of?(Symbol) ? :to_sym : :itself)
|
15
15
|
end
|
16
16
|
|
17
17
|
def dedasherize_names(*args)
|
@@ -22,7 +22,7 @@ module Sinja
|
|
22
22
|
return enum_for(__callee__, hash) unless block_given?
|
23
23
|
|
24
24
|
hash.each do |k, v|
|
25
|
-
yield dedasherize(k), Hash
|
25
|
+
yield dedasherize(k), v.is_a?(Hash) ? dedasherize_names(v) : v
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
@@ -48,12 +48,12 @@ module Sinja
|
|
48
48
|
options.delete(:exclude) || []
|
49
49
|
|
50
50
|
if included.empty?
|
51
|
-
included = Array
|
51
|
+
included = default.is_a?(Array) ? default : default.split(',')
|
52
52
|
|
53
53
|
return included if included.empty?
|
54
54
|
end
|
55
55
|
|
56
|
-
excluded = Array
|
56
|
+
excluded = excluded.is_a?(Array) ? excluded : excluded.split(',')
|
57
57
|
unless excluded.empty?
|
58
58
|
excluded = Set.new(excluded)
|
59
59
|
included.delete_if do |termstr|
|
@@ -79,28 +79,29 @@ module Sinja
|
|
79
79
|
# Move cursor through each term, avoiding the default proc,
|
80
80
|
# halting if no roles found, i.e. client asked to include
|
81
81
|
# something that Sinja doesn't know about
|
82
|
-
throw :keep?, true
|
83
|
-
|
82
|
+
throw :keep?, true unless config =
|
83
|
+
settings._sinja.resource_config.fetch(term.pluralize.to_sym, nil)
|
84
84
|
end
|
85
85
|
|
86
|
-
roles =
|
87
|
-
config.dig(:has_many, last_term.pluralize.to_sym, :fetch) ||
|
88
|
-
config.dig(:has_one, last_term.singularize.to_sym, :pluck)
|
89
|
-
)[:roles]
|
86
|
+
throw :keep?, true unless roles =
|
87
|
+
config.dig(:has_many, last_term.pluralize.to_sym, :fetch, :roles) ||
|
88
|
+
config.dig(:has_one, last_term.singularize.to_sym, :pluck, :roles)
|
90
89
|
|
91
|
-
throw :keep?, roles && (roles.empty? || roles
|
90
|
+
throw :keep?, roles && (roles.empty? || roles.intersect?(memoized_role))
|
92
91
|
end
|
93
92
|
end
|
94
93
|
end
|
95
94
|
|
96
95
|
def serialize_model(model=nil, options={})
|
97
96
|
options[:is_collection] = false
|
98
|
-
options[:skip_collection_check] = defined?(::Sequel) && ::Sequel::Model
|
97
|
+
options[:skip_collection_check] = defined?(::Sequel) && model.is_a?(::Sequel::Model)
|
99
98
|
options[:include] = include_exclude!(options)
|
100
99
|
options[:fields] ||= params[:fields] unless params[:fields].empty?
|
101
100
|
options = settings._sinja.serializer_opts.merge(options)
|
102
101
|
|
103
102
|
::JSONAPI::Serializer.serialize(model, options)
|
103
|
+
rescue ::JSONAPI::Serializer::InvalidIncludeError=>e
|
104
|
+
raise BadRequestError, e
|
104
105
|
end
|
105
106
|
|
106
107
|
def serialize_model?(model=nil, options={})
|
@@ -122,7 +123,7 @@ module Sinja
|
|
122
123
|
if pagination
|
123
124
|
# Whitelist pagination keys and dasherize query parameter names
|
124
125
|
pagination = VALID_PAGINATION_KEYS
|
125
|
-
.select
|
126
|
+
.select(&pagination.method(:key?))
|
126
127
|
.map! do |outer_key|
|
127
128
|
[outer_key, pagination[outer_key].map do |inner_key, value|
|
128
129
|
[inner_key.to_s.dasherize.to_sym, value]
|
@@ -151,11 +152,13 @@ module Sinja
|
|
151
152
|
end.to_h)
|
152
153
|
end
|
153
154
|
|
154
|
-
::JSONAPI::Serializer.serialize(
|
155
|
+
::JSONAPI::Serializer.serialize(Array(models), options)
|
156
|
+
rescue ::JSONAPI::Serializer::InvalidIncludeError=>e
|
157
|
+
raise BadRequestError, e
|
155
158
|
end
|
156
159
|
|
157
160
|
def serialize_models?(models=[], options={}, pagination=nil)
|
158
|
-
if
|
161
|
+
if Array(models).any?
|
159
162
|
body serialize_models(models, options, pagination)
|
160
163
|
elsif options.key?(:meta)
|
161
164
|
body serialize_models([], :meta=>options[:meta])
|
@@ -166,7 +169,7 @@ module Sinja
|
|
166
169
|
|
167
170
|
def serialize_linkage(model, rel, options={})
|
168
171
|
options[:is_collection] = false
|
169
|
-
options[:skip_collection_check] = defined?(::Sequel) && ::Sequel::Model
|
172
|
+
options[:skip_collection_check] = defined?(::Sequel::Model) && model.is_a?(::Sequel::Model)
|
170
173
|
options[:include] = rel.to_s
|
171
174
|
options = settings._sinja.serializer_opts.merge(options)
|
172
175
|
|
@@ -202,17 +205,18 @@ module Sinja
|
|
202
205
|
e.respond_to?(:title) ? e.title : e.class.name.demodulize.titleize
|
203
206
|
end
|
204
207
|
|
205
|
-
def serialize_errors
|
208
|
+
def serialize_errors
|
206
209
|
raise env['sinatra.error'] if env['sinatra.error'] && sideloaded?
|
207
210
|
|
211
|
+
abody = Array(body)
|
208
212
|
error_hashes =
|
209
|
-
if
|
210
|
-
if
|
213
|
+
if abody.any?
|
214
|
+
if abody.all? { |error| error.is_a?(Hash) }
|
211
215
|
# `halt' with a hash or array of hashes
|
212
|
-
|
216
|
+
abody.flat_map(&method(:error_hash))
|
213
217
|
elsif not_found?
|
214
218
|
# `not_found' or `halt 404'
|
215
|
-
message =
|
219
|
+
message = abody.first.to_s
|
216
220
|
error_hash \
|
217
221
|
:title=>'Not Found Error',
|
218
222
|
:detail=>(message unless message == '<h1>Not Found</h1>')
|
@@ -220,7 +224,7 @@ module Sinja
|
|
220
224
|
# `halt'
|
221
225
|
error_hash \
|
222
226
|
:title=>'Unknown Error',
|
223
|
-
:detail=>
|
227
|
+
:detail=>abody.first.to_s
|
224
228
|
end
|
225
229
|
end
|
226
230
|
|
@@ -231,12 +235,12 @@ module Sinja
|
|
231
235
|
error_hashes ||=
|
232
236
|
case e = env['sinatra.error']
|
233
237
|
when UnprocessibleEntityError
|
234
|
-
e.tuples.flat_map do |
|
238
|
+
e.tuples.flat_map do |key, full_message, type=:attributes|
|
235
239
|
error_hash \
|
236
240
|
:title=>exception_title(e),
|
237
241
|
:detail=>full_message.to_s,
|
238
242
|
:source=>{
|
239
|
-
:pointer=>(
|
243
|
+
:pointer=>(key ? "/data/#{type}/#{key.to_s.dasherize}" : '/data')
|
240
244
|
}
|
241
245
|
end
|
242
246
|
when Exception
|
@@ -248,7 +252,9 @@ module Sinja
|
|
248
252
|
:title=>'Unknown Error'
|
249
253
|
end
|
250
254
|
|
251
|
-
|
255
|
+
if block = settings._sinja.error_logger
|
256
|
+
error_hashes.each { |h| instance_exec(h, &block) }
|
257
|
+
end
|
252
258
|
|
253
259
|
content_type :api_json
|
254
260
|
JSON.send settings._sinja.json_error_generator,
|
data/lib/sinja/resource.rb
CHANGED
@@ -27,10 +27,10 @@ module Sinja
|
|
27
27
|
|
28
28
|
context.define_singleton_method(action) do |**opts, &block|
|
29
29
|
abort "Unexpected option(s) for `#{action}' action helper" \
|
30
|
-
unless (opts.keys -
|
30
|
+
unless (opts.keys - Array(allow_opts)).empty?
|
31
31
|
|
32
32
|
resource_config[action].each do |k, v|
|
33
|
-
v.replace(
|
33
|
+
v.replace(Array(opts[k])) if opts.key?(k)
|
34
34
|
end
|
35
35
|
|
36
36
|
return unless block ||=
|
@@ -57,9 +57,9 @@ module Sinja
|
|
57
57
|
case result = instance_exec(*args, &block)
|
58
58
|
when Array
|
59
59
|
opts = {}
|
60
|
-
if
|
60
|
+
if result.last.instance_of?(Hash)
|
61
61
|
opts = result.pop
|
62
|
-
elsif required_arity < 0 && !
|
62
|
+
elsif required_arity < 0 && !result.first.is_a?(Array)
|
63
63
|
result = [result]
|
64
64
|
end
|
65
65
|
|
@@ -97,7 +97,7 @@ module Sinja
|
|
97
97
|
parent = sideloaded? && env['sinja.passthru'].to_sym
|
98
98
|
|
99
99
|
roles, sideload_on = config.fetch(action, {}).values_at(:roles, :sideload_on)
|
100
|
-
roles.nil? || roles.empty? || roles
|
100
|
+
roles.nil? || roles.empty? || roles.intersect?(memoized_role) ||
|
101
101
|
parent && sideload_on.include?(parent) && super(parent, *args)
|
102
102
|
end
|
103
103
|
|
@@ -15,8 +15,8 @@ module Sinja
|
|
15
15
|
|
16
16
|
app.get '', :qcaptures=>{ :filter=>:id }, :qparams=>%i[include fields], :actions=>:show do
|
17
17
|
ids = @qcaptures.first # TODO: Get this as a block parameter?
|
18
|
-
ids = ids.split(',') if String
|
19
|
-
ids =
|
18
|
+
ids = ids.split(',') if ids.instance_of?(String)
|
19
|
+
ids = Array(ids).tap(&:uniq!)
|
20
20
|
|
21
21
|
resources, opts =
|
22
22
|
if respond_to?(:show_many)
|
@@ -49,7 +49,7 @@ module Sinja
|
|
49
49
|
serialize_models(collection, opts, pagination)
|
50
50
|
end
|
51
51
|
|
52
|
-
app.post '', :qparams
|
52
|
+
app.post '', :qparams=>%i[include fields], :actions=>:create do
|
53
53
|
sanity_check!
|
54
54
|
|
55
55
|
opts = {}
|
@@ -92,7 +92,7 @@ module Sinja
|
|
92
92
|
serialize_model(tmp, opts)
|
93
93
|
end
|
94
94
|
|
95
|
-
app.patch '/:id', :qparams
|
95
|
+
app.patch '/:id', :qparams=>%i[include fields], :actions=>:update do |id|
|
96
96
|
sanity_check!(id)
|
97
97
|
tmp, opts = transaction do
|
98
98
|
update(attributes).tap do
|
data/lib/sinja/version.rb
CHANGED
data/sinja.gemspec
CHANGED
@@ -18,15 +18,16 @@ Gem::Specification.new do |spec|
|
|
18
18
|
and it configures Sinatra with the proper settings, MIME-types, filters,
|
19
19
|
conditions, and error-handling.
|
20
20
|
|
21
|
-
There are many parsing (deserializing)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
There are many parsing (deserializing), rendering (serializing), and other
|
22
|
+
"JSON API" libraries available for Ruby, but relatively few that attempt to
|
23
|
+
correctly implement the entire {json:api} server specification, including
|
24
|
+
routing, request header and query parameter checking, and relationship
|
25
|
+
side-loading. Sinja lets you focus on the business logic of your
|
26
|
+
applications without worrying about the specification, and without pulling
|
27
|
+
in a heavy framework like Rails. It's lightweight, ORM-agnostic, and
|
28
|
+
Ember.js-friendly!
|
28
29
|
EOF
|
29
|
-
spec.homepage = '
|
30
|
+
spec.homepage = 'http://sinja-rb.org'
|
30
31
|
spec.license = 'MIT'
|
31
32
|
|
32
33
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sinja
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Pastore
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -216,13 +216,14 @@ description: |
|
|
216
216
|
and it configures Sinatra with the proper settings, MIME-types, filters,
|
217
217
|
conditions, and error-handling.
|
218
218
|
|
219
|
-
There are many parsing (deserializing)
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
219
|
+
There are many parsing (deserializing), rendering (serializing), and other
|
220
|
+
"JSON API" libraries available for Ruby, but relatively few that attempt to
|
221
|
+
correctly implement the entire {json:api} server specification, including
|
222
|
+
routing, request header and query parameter checking, and relationship
|
223
|
+
side-loading. Sinja lets you focus on the business logic of your
|
224
|
+
applications without worrying about the specification, and without pulling
|
225
|
+
in a heavy framework like Rails. It's lightweight, ORM-agnostic, and
|
226
|
+
Ember.js-friendly!
|
226
227
|
email:
|
227
228
|
- mike@oobak.org
|
228
229
|
executables: []
|
@@ -264,7 +265,7 @@ files:
|
|
264
265
|
- lib/sinja/resource_routes.rb
|
265
266
|
- lib/sinja/version.rb
|
266
267
|
- sinja.gemspec
|
267
|
-
homepage:
|
268
|
+
homepage: http://sinja-rb.org
|
268
269
|
licenses:
|
269
270
|
- MIT
|
270
271
|
metadata: {}
|