shaf 1.4.1 → 1.5.0
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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +3 -2
- data/lib/shaf/extensions.rb +2 -0
- data/lib/shaf/extensions/log.rb +19 -0
- data/lib/shaf/helpers/json_html.rb +5 -2
- data/lib/shaf/helpers/payload.rb +6 -4
- data/lib/shaf/responder/base.rb +56 -33
- data/lib/shaf/responder/hal.rb +6 -2
- data/lib/shaf/responder/hal_serializable.rb +54 -0
- data/lib/shaf/responder/html.rb +6 -2
- data/lib/shaf/version.rb +1 -1
- data/templates/api/controllers/base_controller.rb +13 -23
- data/templates/config/directories.rb +0 -1
- data/templates/config/paths.rb +9 -5
- data/templates/frontend/views/form.erb +1 -1
- data/templates/frontend/views/payload.erb +1 -1
- data/upgrades/1.5.0.tar.gz +0 -0
- metadata +19 -2
- metadata.gz.sig +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21af40642344c3526a2b34df81fe891ce407ac8ef822e125938d8ecc415fcba0
|
4
|
+
data.tar.gz: 8be6b04b3a460979d6494fda33a5a88797b23d6c3266bb51e5505184b70707da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 505ab287d131637d261d3391eeb58517cfe9aa6160d1e51bfb619544299bfea360b90a63ffb1ad0d2c70edaf4da7ca64c84f4de3d624a001c07d2eb0fdb27c10
|
7
|
+
data.tar.gz: 8dc0a97ed2b062b8bbb85cbcdce50a624e42ca010c12638a998deb33d9e7d1438d18a04d58ac8cdb5339286f9744a2948f8c63e0f739636b61940c35b0b8f0db
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
@@ -1,2 +1,3 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
)��pK�iІ�����j��p#�nt��g�CY�]��Y�
|
2
|
+
�����u*wS1�^�S�3�r��|�\�2��a�I2�����A��5�$���Ov����=�IG&`.����Ta^Up��X^F
|
3
|
+
�Q��+���vC�+L��yi��*��vӥ���j��~i��b�zQuI���}���l��D�����~+����Z�MI��2�J%8�J9�*H��jlT� 5ʬ���uNs��GV8��v+R��dO��3�p�,��
|
data/lib/shaf/extensions.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'shaf/extensions/log'
|
1
2
|
require 'shaf/extensions/resource_uris'
|
2
3
|
require 'shaf/extensions/controller_hooks'
|
3
4
|
require 'shaf/extensions/current_user'
|
@@ -7,6 +8,7 @@ require 'shaf/extensions/symbolic_routes'
|
|
7
8
|
module Shaf
|
8
9
|
def self.extensions
|
9
10
|
[
|
11
|
+
Log,
|
10
12
|
ResourceUris,
|
11
13
|
ControllerHooks,
|
12
14
|
CurrentUser,
|
data/lib/shaf/helpers/payload.rb
CHANGED
@@ -66,7 +66,7 @@ module Shaf
|
|
66
66
|
@profile = value
|
67
67
|
end
|
68
68
|
|
69
|
-
def respond_with_collection(resource, status:
|
69
|
+
def respond_with_collection(resource, status: nil, serializer: nil, preload: [], **kwargs)
|
70
70
|
respond_with(
|
71
71
|
resource,
|
72
72
|
status: status,
|
@@ -76,7 +76,8 @@ module Shaf
|
|
76
76
|
)
|
77
77
|
end
|
78
78
|
|
79
|
-
def respond_with(resource, status:
|
79
|
+
def respond_with(resource, status: nil, serializer: nil, collection: false, preload: [], **kwargs)
|
80
|
+
status ||= resource.respond_to?(:http_status) ? resource.http_status : 200
|
80
81
|
status(status)
|
81
82
|
|
82
83
|
kwargs.merge!(
|
@@ -86,15 +87,16 @@ module Shaf
|
|
86
87
|
)
|
87
88
|
|
88
89
|
log.info "#{request.request_method} #{request.path_info} => #{status}"
|
89
|
-
payload = Responder.for(request, resource).call(self, resource, **kwargs)
|
90
|
+
payload = Responder.for(request, resource).call(self, resource, preload: preload, **kwargs)
|
90
91
|
add_cache_headers(payload, kwargs)
|
92
|
+
payload
|
91
93
|
rescue StandardError => err
|
92
94
|
log.error "Failure: #{err.message}\n#{err.backtrace}"
|
93
95
|
if status == 500
|
94
96
|
content_type mime_type(:json)
|
95
97
|
body JSON.generate(failure: err.message)
|
96
98
|
else
|
97
|
-
respond_with(Errors::ServerError.new(err.message)
|
99
|
+
respond_with(Errors::ServerError.new(err.message))
|
98
100
|
end
|
99
101
|
end
|
100
102
|
|
data/lib/shaf/responder/base.rb
CHANGED
@@ -1,17 +1,23 @@
|
|
1
1
|
module Shaf
|
2
2
|
module Responder
|
3
3
|
class Response
|
4
|
-
attr_reader :content_type, :body, :
|
4
|
+
attr_reader :content_type, :body, :serialized_hash, :resource
|
5
5
|
|
6
|
-
def initialize(content_type
|
6
|
+
def initialize(content_type:, body:, serialized_hash: {}, resource: nil)
|
7
7
|
@content_type = content_type
|
8
8
|
@body = body
|
9
|
+
@serialized_hash = serialized_hash
|
9
10
|
@resource = resource
|
10
|
-
|
11
|
+
end
|
12
|
+
|
13
|
+
def log_entry
|
14
|
+
"Response (#{resource.class}) payload: #{body}"
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
14
18
|
class Base
|
19
|
+
PRELOAD_FAILED_MSG = "Failed to preload '%s'. Link could not be extracted from response"
|
20
|
+
|
15
21
|
class << self
|
16
22
|
def mime_type(type = nil, value = nil)
|
17
23
|
if type
|
@@ -29,11 +35,12 @@ module Shaf
|
|
29
35
|
Responder.default = self
|
30
36
|
end
|
31
37
|
|
32
|
-
def call(controller, resource, **kwargs)
|
38
|
+
def call(controller, resource, preload: [], **kwargs)
|
33
39
|
responder = new(controller, resource, **kwargs)
|
34
|
-
response = responder.
|
40
|
+
response = responder.build_response
|
35
41
|
log_response(controller, response)
|
36
|
-
|
42
|
+
preload_links = preload_links(preload, responder, response, controller)
|
43
|
+
write_response(controller, response, preload_links)
|
37
44
|
end
|
38
45
|
|
39
46
|
def can_handle?(_obj)
|
@@ -43,17 +50,43 @@ module Shaf
|
|
43
50
|
private
|
44
51
|
|
45
52
|
def log_response(controller, response)
|
53
|
+
log(controller, response.log_entry)
|
54
|
+
end
|
55
|
+
|
56
|
+
def log(controller, msg, type: :debug)
|
46
57
|
return unless controller.respond_to? :log
|
58
|
+
controller.log.send(type, msg)
|
59
|
+
end
|
47
60
|
|
48
|
-
|
49
|
-
|
50
|
-
|
61
|
+
def preload_links(rels, responder, response, controller = nil)
|
62
|
+
Array(rels).map do |rel|
|
63
|
+
links = responder.lookup_rel(rel, response)
|
64
|
+
links = [links].compact unless links.is_a? Array
|
65
|
+
log(controller, PRELOAD_FAILED_MSG % rel) if links.empty?
|
66
|
+
links
|
67
|
+
end
|
51
68
|
end
|
52
69
|
|
53
|
-
def write_response(controller, response)
|
70
|
+
def write_response(controller, response, preload_links)
|
54
71
|
controller.content_type(response.content_type)
|
72
|
+
add_preload_links(controller, response, preload_links)
|
55
73
|
controller.body(response.body)
|
56
74
|
end
|
75
|
+
|
76
|
+
def add_preload_links(controller, response, preload_links)
|
77
|
+
preload_links.each do |links|
|
78
|
+
links.each do |link|
|
79
|
+
next unless link[:href]
|
80
|
+
# Nginx http2_push_preload only processes relative URIs with absolute path
|
81
|
+
href = link[:href].sub(%r{https?://[^/]+}, "")
|
82
|
+
type = link.fetch(:as, 'fetch')
|
83
|
+
xorigin = link.fetch(:crossorigin, 'anonymous')
|
84
|
+
links = (controller.headers['Link'] || "").split(',').map(&:strip)
|
85
|
+
links << "<#{href}>; rel=preload; as=#{type}; crossorigin=#{xorigin}"
|
86
|
+
controller.headers["Link"] = links.join(', ') unless links.empty?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
57
90
|
end
|
58
91
|
|
59
92
|
attr_reader :controller, :resource, :options
|
@@ -68,12 +101,21 @@ module Shaf
|
|
68
101
|
raise NotImplementedError, "#{self.class} must implement #body"
|
69
102
|
end
|
70
103
|
|
71
|
-
def
|
72
|
-
|
104
|
+
def serialized_hash
|
105
|
+
{}
|
73
106
|
end
|
74
107
|
|
75
|
-
def
|
76
|
-
Response.new(
|
108
|
+
def build_response
|
109
|
+
Response.new(
|
110
|
+
content_type: mime_type,
|
111
|
+
body: body,
|
112
|
+
serialized_hash: serialized_hash,
|
113
|
+
resource: resource
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def lookup_rel(_rel, _response)
|
118
|
+
[]
|
77
119
|
end
|
78
120
|
|
79
121
|
private
|
@@ -82,30 +124,11 @@ module Shaf
|
|
82
124
|
self.class.mime_type
|
83
125
|
end
|
84
126
|
|
85
|
-
def collection?
|
86
|
-
!!options.fetch(:collection, false)
|
87
|
-
end
|
88
|
-
|
89
127
|
def user
|
90
128
|
options.fetch(:current_user) do
|
91
129
|
controller.current_user if controller.respond_to? :current_user
|
92
130
|
end
|
93
131
|
end
|
94
|
-
|
95
|
-
def serializer
|
96
|
-
@serializer ||= options[:serializer] || HALPresenter.lookup_presenter(resource)
|
97
|
-
end
|
98
|
-
|
99
|
-
def serialize
|
100
|
-
return "" unless serializer
|
101
|
-
|
102
|
-
@serialize ||=
|
103
|
-
if collection?
|
104
|
-
serializer.to_collection(resource, current_user: user, **options)
|
105
|
-
else
|
106
|
-
serializer.to_hal(resource, current_user: user, **options)
|
107
|
-
end
|
108
|
-
end
|
109
132
|
end
|
110
133
|
end
|
111
134
|
end
|
data/lib/shaf/responder/hal.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
|
+
require 'shaf/responder/hal_serializable'
|
2
|
+
|
1
3
|
module Shaf
|
2
4
|
module Responder
|
3
5
|
class Hal < Base
|
4
|
-
|
6
|
+
include HalSerializable
|
7
|
+
|
5
8
|
use_as_default!
|
9
|
+
mime_type :hal, 'application/hal+json'
|
6
10
|
|
7
11
|
def body
|
8
|
-
|
12
|
+
@body ||= JSON.generate(serialized_hash)
|
9
13
|
end
|
10
14
|
|
11
15
|
private
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'hal_presenter'
|
2
|
+
require 'shaf/errors'
|
3
|
+
|
4
|
+
module Shaf
|
5
|
+
module Responder
|
6
|
+
module HalSerializable
|
7
|
+
def lookup_rel(rel, response)
|
8
|
+
hal = response.serialized_hash
|
9
|
+
return [] unless hal
|
10
|
+
|
11
|
+
links = hal.dig(:_links, rel.to_sym)
|
12
|
+
return [] unless links
|
13
|
+
|
14
|
+
links = [links] unless links.is_a? Array
|
15
|
+
links.map do |link|
|
16
|
+
{
|
17
|
+
href: link[:href],
|
18
|
+
as: 'fetch',
|
19
|
+
crossorigin: 'anonymous'
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def collection?
|
25
|
+
!!options.fetch(:collection, false)
|
26
|
+
end
|
27
|
+
|
28
|
+
def serializer
|
29
|
+
@serializer ||= options[:serializer] || HALPresenter.lookup_presenter(resource)
|
30
|
+
end
|
31
|
+
|
32
|
+
def serialized_hash
|
33
|
+
return {} unless serializer
|
34
|
+
|
35
|
+
@serialized_hash ||=
|
36
|
+
if collection?
|
37
|
+
serializer.to_collection(resource, current_user: user, as_hash: true, **options)
|
38
|
+
else
|
39
|
+
serializer.to_hal(resource, current_user: user, as_hash: true, **options)
|
40
|
+
end
|
41
|
+
|
42
|
+
# hal_presenter versions before v1.5.0 does not understand the :as_hash
|
43
|
+
# keyword argument and will always return a String from
|
44
|
+
# to_hal/to_collection, thus we need to parse it if its a String.
|
45
|
+
if @serialized_hash.is_a? String
|
46
|
+
@body = @serialized_hash
|
47
|
+
@serialized_hash = JSON.parse(@serialized_hash, symbolize_names: true)
|
48
|
+
end
|
49
|
+
|
50
|
+
@serialized_hash
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/shaf/responder/html.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
|
+
require 'shaf/responder/hal_serializable'
|
2
|
+
|
1
3
|
module Shaf
|
2
4
|
module Responder
|
3
5
|
class Html < Base
|
6
|
+
include HalSerializable
|
7
|
+
|
4
8
|
mime_type :html
|
5
9
|
|
6
10
|
def body
|
7
11
|
case resource
|
8
12
|
when Formable::Form
|
9
|
-
controller.erb(:form, locals: {form: resource, serialized:
|
13
|
+
controller.erb(:form, locals: {form: resource, serialized: serialized_hash})
|
10
14
|
else
|
11
|
-
controller.erb(:payload, locals: {serialized:
|
15
|
+
controller.erb(:payload, locals: {serialized: serialized_hash})
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
data/lib/shaf/version.rb
CHANGED
@@ -19,19 +19,6 @@ class BaseController < Sinatra::Base
|
|
19
19
|
|
20
20
|
Shaf::Router.mount(self, default: true)
|
21
21
|
|
22
|
-
def self.inherited(controller)
|
23
|
-
super
|
24
|
-
Shaf::Router.mount controller
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.log
|
28
|
-
$logger
|
29
|
-
end
|
30
|
-
|
31
|
-
def log
|
32
|
-
self.class.log
|
33
|
-
end
|
34
|
-
|
35
22
|
register(*Shaf.extensions)
|
36
23
|
helpers(*Shaf.helpers)
|
37
24
|
|
@@ -42,13 +29,6 @@ class BaseController < Sinatra::Base
|
|
42
29
|
set_vary_header
|
43
30
|
end
|
44
31
|
|
45
|
-
def set_vary_header
|
46
|
-
return unless settings.auth_token_header
|
47
|
-
return unless [:get, :head].include? request.request_method.downcase.to_sym
|
48
|
-
|
49
|
-
headers('Vary' => Shaf::Settings.auth_token_header)
|
50
|
-
end
|
51
|
-
|
52
32
|
not_found do
|
53
33
|
err = NotFoundError.new "Resource \"#{request.path_info}\" does not exist"
|
54
34
|
respond_with(err, status: err.http_status)
|
@@ -59,12 +39,22 @@ class BaseController < Sinatra::Base
|
|
59
39
|
log.error err.message
|
60
40
|
Array(err.backtrace).each(&log.method(:error))
|
61
41
|
|
62
|
-
api_error
|
42
|
+
respond_with api_error(err)
|
43
|
+
end
|
63
44
|
|
64
|
-
|
45
|
+
def self.inherited(controller)
|
46
|
+
super
|
47
|
+
Shaf::Router.mount controller
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_vary_header
|
51
|
+
return unless settings.auth_token_header
|
52
|
+
return unless [:get, :head].include? request.request_method.downcase.to_sym
|
53
|
+
|
54
|
+
headers('Vary' => Shaf::Settings.auth_token_header)
|
65
55
|
end
|
66
56
|
|
67
|
-
def
|
57
|
+
def api_error(err)
|
68
58
|
case err
|
69
59
|
when Shaf::Authorize::PolicyViolationError
|
70
60
|
ForbiddenError.new
|
data/templates/config/paths.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
app_root = File.expand_path('../', __dir__)
|
4
|
-
|
5
|
-
Shaf::Settings.
|
6
|
-
Shaf::Settings.
|
7
|
-
Shaf::Settings.
|
8
|
-
Shaf::Settings.
|
4
|
+
[
|
5
|
+
Shaf::Settings.app_root = app_root,
|
6
|
+
Shaf::Settings.app_dir = File.expand_path('api', app_root),
|
7
|
+
Shaf::Settings.src_dir = File.expand_path('src', app_root),
|
8
|
+
Shaf::Settings.lib_dir = File.expand_path('lib', app_root),
|
9
|
+
Shaf::Settings.spec_dir = File.expand_path('spec', app_root)
|
10
|
+
].each do |dir|
|
11
|
+
$LOAD_PATH.unshift(dir) if Dir.exist? dir
|
12
|
+
end
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shaf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sammy Henningsson
|
@@ -30,7 +30,7 @@ cert_chain:
|
|
30
30
|
ZMhjYR7sRczGJx+GxGU2EaR0bjRsPVlC4ywtFxoOfRG3WaJcpWGEoAoMJX6Z0bRv
|
31
31
|
M40=
|
32
32
|
-----END CERTIFICATE-----
|
33
|
-
date:
|
33
|
+
date: 2020-02-02 00:00:00.000000000 Z
|
34
34
|
dependencies:
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: minitest
|
@@ -178,6 +178,20 @@ dependencies:
|
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '2.1'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: hal_presenter
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '1.5'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '1.5'
|
181
195
|
description: A framework for building hypermedia driven APIs with sinatra and sequel.
|
182
196
|
email: sammy.henningsson@gmail.com
|
183
197
|
executables:
|
@@ -208,6 +222,7 @@ files:
|
|
208
222
|
- lib/shaf/extensions/authorize.rb
|
209
223
|
- lib/shaf/extensions/controller_hooks.rb
|
210
224
|
- lib/shaf/extensions/current_user.rb
|
225
|
+
- lib/shaf/extensions/log.rb
|
211
226
|
- lib/shaf/extensions/resource_uris.rb
|
212
227
|
- lib/shaf/extensions/symbolic_routes.rb
|
213
228
|
- lib/shaf/formable.rb
|
@@ -258,6 +273,7 @@ files:
|
|
258
273
|
- lib/shaf/responder.rb
|
259
274
|
- lib/shaf/responder/base.rb
|
260
275
|
- lib/shaf/responder/hal.rb
|
276
|
+
- lib/shaf/responder/hal_serializable.rb
|
261
277
|
- lib/shaf/responder/html.rb
|
262
278
|
- lib/shaf/responder/problem_json.rb
|
263
279
|
- lib/shaf/router.rb
|
@@ -328,6 +344,7 @@ files:
|
|
328
344
|
- upgrades/1.2.1.tar.gz
|
329
345
|
- upgrades/1.3.0.tar.gz
|
330
346
|
- upgrades/1.4.0.tar.gz
|
347
|
+
- upgrades/1.5.0.tar.gz
|
331
348
|
homepage:
|
332
349
|
licenses:
|
333
350
|
- MIT
|
metadata.gz.sig
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
|
1
|
+
ZN��l�|��>�d�
|
2
|
+
ne�H��t̊9��%����D
|