shaf 1.6.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/iana_link_relations.csv.gz +0 -0
- data/lib/shaf/alps/attribute_serializer.rb +41 -0
- data/lib/shaf/alps/json_serializer.rb +50 -0
- data/lib/shaf/alps/relation_serializer.rb +70 -0
- data/lib/shaf/app.rb +32 -7
- data/lib/shaf/authenticator/base.rb +161 -0
- data/lib/shaf/authenticator/basic_auth.rb +25 -0
- data/lib/shaf/authenticator/challenge.rb +32 -0
- data/lib/shaf/authenticator/parameter.rb +31 -0
- data/lib/shaf/authenticator/request.rb +17 -0
- data/lib/shaf/authenticator.rb +56 -0
- data/lib/shaf/command/base.rb +4 -0
- data/lib/shaf/command/console.rb +1 -1
- data/lib/shaf/command/generate.rb +5 -2
- data/lib/shaf/command/new.rb +24 -7
- data/lib/shaf/command/server.rb +5 -1
- data/lib/shaf/command/templates/Gemfile.erb +1 -0
- data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +9 -12
- data/lib/shaf/errors.rb +11 -0
- data/lib/shaf/extensions/api_routes.rb +60 -0
- data/lib/shaf/extensions/authorize.rb +11 -9
- data/lib/shaf/extensions/log.rb +1 -1
- data/lib/shaf/extensions/resource_uris.rb +95 -137
- data/lib/shaf/extensions/symbolic_routes.rb +9 -19
- data/lib/shaf/extensions.rb +3 -3
- data/lib/shaf/formable/builder.rb +58 -19
- data/lib/shaf/formable/form.rb +14 -11
- data/lib/shaf/formable.rb +10 -24
- data/lib/shaf/generator/base.rb +84 -3
- data/lib/shaf/generator/controller.rb +30 -42
- data/lib/shaf/generator/doc.rb +17 -0
- data/lib/shaf/generator/forms.rb +11 -14
- data/lib/shaf/generator/helper.rb +2 -1
- data/lib/shaf/generator/migration/add_column.rb +0 -4
- data/lib/shaf/generator/migration/add_index.rb +0 -4
- data/lib/shaf/generator/migration/base.rb +15 -3
- data/lib/shaf/generator/migration/create_table.rb +0 -4
- data/lib/shaf/generator/migration/drop_column.rb +0 -4
- data/lib/shaf/generator/migration/rename_column.rb +0 -4
- data/lib/shaf/generator/migration/type.rb +4 -26
- data/lib/shaf/generator/migration/types.rb +45 -16
- data/lib/shaf/generator/model.rb +29 -15
- data/lib/shaf/generator/policy.rb +8 -14
- data/lib/shaf/generator/profile.rb +47 -0
- data/lib/shaf/generator/scaffold.rb +6 -9
- data/lib/shaf/generator/serializer.rb +64 -98
- data/lib/shaf/generator/templates/api/controller.rb.erb +13 -13
- data/lib/shaf/generator/templates/api/forms.rb.erb +2 -2
- data/lib/shaf/generator/templates/api/model.rb.erb +1 -1
- data/lib/shaf/generator/templates/api/policy.rb.erb +2 -2
- data/lib/shaf/generator/templates/api/profile.rb.erb +16 -0
- data/lib/shaf/generator/templates/api/serializer.rb.erb +3 -3
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +15 -16
- data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
- data/lib/shaf/generator.rb +2 -0
- data/lib/shaf/helpers/authentication.rb +79 -0
- data/lib/shaf/helpers/paginate.rb +1 -1
- data/lib/shaf/helpers/payload.rb +14 -34
- data/lib/shaf/helpers/vary.rb +8 -0
- data/lib/shaf/helpers.rb +4 -0
- data/lib/shaf/logger.rb +12 -0
- data/lib/shaf/parser/base.rb +44 -0
- data/lib/shaf/parser/form_data.rb +15 -0
- data/lib/shaf/parser/json.rb +26 -0
- data/lib/shaf/parser.rb +65 -0
- data/lib/shaf/profile/attribute.rb +29 -0
- data/lib/shaf/profile/evaluator.rb +46 -0
- data/lib/shaf/profile/relation.rb +29 -0
- data/lib/shaf/profile/unique_id.rb +58 -0
- data/lib/shaf/profile.rb +115 -0
- data/lib/shaf/profiles/shaf_basic.rb +20 -0
- data/lib/shaf/profiles/shaf_error.rb +49 -0
- data/lib/shaf/profiles/shaf_form.rb +110 -0
- data/lib/shaf/profiles.rb +46 -0
- data/lib/shaf/registrable_factory.rb +62 -32
- data/lib/shaf/responder/alps_json.rb +25 -0
- data/lib/shaf/responder/base.rb +20 -17
- data/lib/shaf/responder/hal.rb +66 -5
- data/lib/shaf/responder/html.rb +43 -16
- data/lib/shaf/responder/problem_json.rb +2 -2
- data/lib/shaf/responder.rb +41 -2
- data/lib/shaf/router.rb +65 -12
- data/lib/shaf/serializer.rb +35 -0
- data/lib/shaf/settings.rb +25 -12
- data/lib/shaf/spec/authenticator.rb +13 -0
- data/lib/shaf/spec/base.rb +1 -1
- data/lib/shaf/spec/http_method_utils.rb +1 -1
- data/lib/shaf/spec/integration_spec.rb +26 -14
- data/lib/shaf/spec/payload_utils.rb +2 -2
- data/lib/shaf/spec.rb +1 -0
- data/lib/shaf/supported_http_methods.rb +15 -0
- data/lib/shaf/tasks/routes_task.rb +14 -17
- data/lib/shaf/tasks.rb +0 -1
- data/lib/shaf/upgrade/manifest.rb +11 -2
- data/lib/shaf/upgrade/package.rb +73 -45
- data/lib/shaf/upgrade/version.rb +11 -10
- data/lib/shaf/utils.rb +19 -5
- data/lib/shaf/version.rb +3 -1
- data/lib/shaf/yard/attribute_method_handler.rb +19 -0
- data/lib/shaf/yard/attribute_object.rb +30 -0
- data/lib/shaf/yard/base_method_handler.rb +30 -0
- data/lib/shaf/yard/link_method_handler.rb +39 -0
- data/lib/shaf/yard/link_object.rb +60 -0
- data/lib/shaf/yard/nested_attributes.rb +37 -0
- data/lib/shaf/yard/parser.rb +64 -0
- data/lib/shaf/yard/profile_method_handler.rb +51 -0
- data/lib/shaf/yard/profile_object.rb +21 -0
- data/lib/shaf/yard/resource_object.rb +55 -0
- data/lib/shaf/yard/serializer_handler.rb +27 -0
- data/lib/shaf/yard.rb +34 -0
- data/lib/shaf.rb +6 -0
- data/templates/Rakefile +0 -6
- data/templates/api/controllers/base_controller.rb +0 -12
- data/templates/api/controllers/docs_controller.rb +5 -3
- data/templates/api/controllers/root_controller.rb +7 -1
- data/templates/api/policies/base_policy.rb +2 -0
- data/templates/api/serializers/base_serializer.rb +1 -3
- data/templates/api/serializers/error_serializer.rb +1 -5
- data/templates/api/serializers/form_serializer.rb +1 -5
- data/templates/api/serializers/root_serializer.rb +0 -11
- data/templates/api/serializers/validation_error_serializer.rb +1 -5
- data/templates/config/bootstrap.rb +1 -2
- data/templates/config/directories.rb +52 -44
- data/templates/config/helpers.rb +1 -1
- data/templates/config/initializers/authentication.rb +18 -0
- data/templates/config/initializers/db_migrations.rb +2 -2
- data/templates/config/initializers/logging.rb +2 -2
- data/templates/config/initializers/middleware.rb +6 -0
- data/templates/config/initializers.rb +52 -8
- data/templates/config.ru +1 -1
- data/templates/spec/spec_helper.rb +5 -0
- data/upgrades/0.5.0.tar.gz +0 -0
- data/upgrades/1.0.4.tar.gz +0 -0
- data/upgrades/1.1.0.tar.gz +0 -0
- data/upgrades/1.6.1.tar.gz +0 -0
- data/upgrades/2.0.0.tar.gz +0 -0
- data/upgrades/3.0.0.tar.gz +0 -0
- data/yard_templates/api_doc/doc_index/html/body.erb +3 -0
- data/yard_templates/api_doc/doc_index/setup.rb +8 -0
- data/yard_templates/api_doc/html/css/api-doc.css +222 -0
- data/yard_templates/api_doc/html/favicon.ico +0 -0
- data/yard_templates/api_doc/html/js/switch_tab.js +17 -0
- data/yard_templates/api_doc/html/setup.rb +59 -0
- data/yard_templates/api_doc/layout/html/footer.erb +3 -0
- data/yard_templates/api_doc/layout/html/header.erb +7 -0
- data/yard_templates/api_doc/layout/html/layout.erb +13 -0
- data/yard_templates/api_doc/layout/setup.rb +24 -0
- data/yard_templates/api_doc/profile/html/attributes.erb +10 -0
- data/yard_templates/api_doc/profile/html/profile.erb +6 -0
- data/yard_templates/api_doc/profile/html/relations.erb +10 -0
- data/yard_templates/api_doc/profile/setup.rb +38 -0
- data/yard_templates/api_doc/profile_attribute/html/attribute.erb +23 -0
- data/yard_templates/api_doc/profile_attribute/setup.rb +21 -0
- data/yard_templates/api_doc/profile_relation/html/relation.erb +37 -0
- data/yard_templates/api_doc/profile_relation/setup.rb +41 -0
- data/yard_templates/api_doc/resource/html/attributes.erb +10 -0
- data/yard_templates/api_doc/resource/html/profile.erb +14 -0
- data/yard_templates/api_doc/resource/html/relations.erb +10 -0
- data/yard_templates/api_doc/resource/html/resource.erb +5 -0
- data/yard_templates/api_doc/resource/setup.rb +56 -0
- data/yard_templates/api_doc/resource_attribute/html/attribute.erb +23 -0
- data/yard_templates/api_doc/resource_attribute/setup.rb +20 -0
- data/yard_templates/api_doc/resource_relation/html/relation.erb +47 -0
- data/yard_templates/api_doc/resource_relation/setup.rb +80 -0
- data/yard_templates/api_doc/setup.rb +31 -0
- data/yard_templates/api_doc/sidebar/html/profile_list.erb +8 -0
- data/yard_templates/api_doc/sidebar/html/search.erb +7 -0
- data/yard_templates/api_doc/sidebar/html/serializer_list.erb +8 -0
- data/yard_templates/api_doc/sidebar/html/sidebar.erb +13 -0
- data/yard_templates/api_doc/sidebar/setup.rb +56 -0
- data.tar.gz.sig +2 -2
- metadata +143 -38
- metadata.gz.sig +0 -0
- data/lib/shaf/api_doc/comment.rb +0 -27
- data/lib/shaf/api_doc/document.rb +0 -137
- data/lib/shaf/extensions/current_user.rb +0 -48
- data/lib/shaf/middleware.rb +0 -1
- data/lib/shaf/responder/hal_serializable.rb +0 -64
- data/lib/shaf/tasks/api_doc_task.rb +0 -125
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'serializers/base_serializer'
|
2
|
-
require '
|
2
|
+
require '<%= policy_file %>'
|
3
3
|
|
4
4
|
class <%= class_name %> < BaseSerializer
|
5
5
|
|
6
6
|
model <%= model_class_name %>
|
7
7
|
policy <%= policy_class_name %>
|
8
8
|
|
9
|
-
<%=
|
9
|
+
<%= print(profile_with_doc) %>
|
10
10
|
|
11
|
-
<%= print_nested(
|
11
|
+
<%= print_nested(attributes_with_doc) %>
|
12
12
|
|
13
13
|
<%= print_nested(links_with_doc) %>
|
14
14
|
|
@@ -4,23 +4,22 @@ describe <%= model_class_name %>, type: :integration do
|
|
4
4
|
|
5
5
|
it "returns a <%= name %>" do
|
6
6
|
<%= name %> = <%= model_class_name %>.create
|
7
|
-
get <%=
|
7
|
+
get <%= resource_name %>_uri(<%= name %>)
|
8
8
|
_(status).must_equal 200
|
9
9
|
_(link_rels).must_include(:self)
|
10
|
-
_(links[:self][:href]).must_equal <%=
|
10
|
+
_(links[:self][:href]).must_equal <%= resource_name %>_uri(<%= name %>)
|
11
11
|
<% params.each do |param| -%>
|
12
12
|
_(attributes).must_include(:'<%= param[0] %>')
|
13
13
|
<% end -%>
|
14
14
|
end
|
15
15
|
|
16
16
|
it "lists all <%= plural_name %>" do
|
17
|
-
<%= model_class_name %>.create
|
18
|
-
<%= model_class_name %>.create
|
17
|
+
2.times { <%= model_class_name %>.create }
|
19
18
|
|
20
|
-
get <%=
|
19
|
+
get <%= collection_name %>_uri
|
21
20
|
_(status).must_equal 200
|
22
21
|
_(link_rels).must_include(:self)
|
23
|
-
_(links[:self][:href]).must_include <%=
|
22
|
+
_(links[:self][:href]).must_include <%= collection_name %>_uri
|
24
23
|
_(embedded(:'<%= plural_name %>').size).must_equal 2
|
25
24
|
|
26
25
|
each_embedded :'<%= plural_name %>' do
|
@@ -32,12 +31,12 @@ describe <%= model_class_name %>, type: :integration do
|
|
32
31
|
end
|
33
32
|
<% if params.size > 0 %>
|
34
33
|
it "can create <%= plural_name %>" do
|
35
|
-
get <%=
|
34
|
+
get <%= collection_name %>_uri
|
36
35
|
|
37
36
|
_(link_rels).must_include(:'create-form')
|
38
37
|
follow_rel :'create-form'
|
39
|
-
_(links[:self][:href]).must_equal new_<%=
|
40
|
-
_(attributes[:href]).must_equal <%=
|
38
|
+
_(links[:self][:href]).must_equal new_<%= resource_name %>_uri
|
39
|
+
_(attributes[:href]).must_equal <%= collection_name %>_uri
|
41
40
|
_(attributes[:method]).must_equal "POST"
|
42
41
|
_(attributes[:name]).must_equal "create-<%= name %>"
|
43
42
|
_(attributes[:title]).must_equal "Create <%= model_class_name %>"
|
@@ -50,9 +49,9 @@ describe <%= model_class_name %>, type: :integration do
|
|
50
49
|
_(link_rels).must_include(:self)
|
51
50
|
_(headers["Location"]).must_equal links[:self][:href]
|
52
51
|
|
53
|
-
get <%=
|
52
|
+
get <%= collection_name %>_uri
|
54
53
|
_(status).must_equal 200
|
55
|
-
_(links[:self][:href]).must_include <%=
|
54
|
+
_(links[:self][:href]).must_include <%= collection_name %>_uri
|
56
55
|
_(embedded(:'<%= plural_name %>').size).must_equal 1
|
57
56
|
|
58
57
|
embedded :'<%= plural_name %>' do
|
@@ -69,14 +68,14 @@ describe <%= model_class_name %>, type: :integration do
|
|
69
68
|
|
70
69
|
it "<%= plural_name %> can be updated" do
|
71
70
|
<%= name %> = <%= model_class_name %>.create
|
72
|
-
get <%=
|
71
|
+
get <%= resource_name %>_uri(<%= name %>)
|
73
72
|
_(status).must_equal 200
|
74
73
|
|
75
74
|
_(link_rels).must_include(:'edit-form')
|
76
75
|
follow_rel :'edit-form'
|
77
76
|
|
78
|
-
_(links[:self][:href]).must_equal edit_<%=
|
79
|
-
_(attributes[:href]).must_equal <%=
|
77
|
+
_(links[:self][:href]).must_equal edit_<%= resource_name %>_uri(<%= name %>)
|
78
|
+
_(attributes[:href]).must_equal <%= resource_name %>_uri(<%= name %>)
|
80
79
|
_(attributes[:method]).must_equal "PUT"
|
81
80
|
_(attributes[:name]).must_equal "update-<%= name %>"
|
82
81
|
_(attributes[:title]).must_equal "Update <%= model_class_name %>"
|
@@ -92,14 +91,14 @@ describe <%= model_class_name %>, type: :integration do
|
|
92
91
|
|
93
92
|
it "<%= plural_name %> can be deleted" do
|
94
93
|
<%= name %> = <%= model_class_name %>.create
|
95
|
-
get <%=
|
94
|
+
get <%= resource_name %>_uri(<%= name %>)
|
96
95
|
_(status).must_equal 200
|
97
96
|
_(link_rels).must_include(:'doc:delete')
|
98
97
|
|
99
98
|
follow_rel(:'doc:delete', method: :delete)
|
100
99
|
_(status).must_equal 204
|
101
100
|
|
102
|
-
get <%=
|
101
|
+
get <%= resource_name %>_uri(<%= name %>)
|
103
102
|
_(status).must_equal 404
|
104
103
|
end
|
105
104
|
|
@@ -15,8 +15,8 @@ describe <%= class_name %> do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
it "serializes attributes" do
|
18
|
-
<%
|
19
|
-
_(attributes.keys).must_include(
|
18
|
+
<% attribute_names.each do |attr| -%>
|
19
|
+
_(attributes.keys).must_include(:<%= attr %>)
|
20
20
|
<% end -%>
|
21
21
|
end
|
22
22
|
|
@@ -33,13 +33,13 @@ describe <%= class_name %> do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
it "serializes attributes" do
|
36
|
-
<%
|
37
|
-
_(attributes.keys).must_include(
|
36
|
+
<% attribute_names.each do |attr| -%>
|
37
|
+
_(attributes.keys).must_include(:<%= attr %>)
|
38
38
|
<% end -%>
|
39
39
|
end
|
40
40
|
|
41
41
|
it "serializes links" do
|
42
|
-
<%
|
42
|
+
<% link_relations.each do |rel| -%>
|
43
43
|
_(link_rels).must_include(:'<%= rel %>')
|
44
44
|
<% end -%>
|
45
45
|
end
|
data/lib/shaf/generator.rb
CHANGED
@@ -10,9 +10,11 @@ end
|
|
10
10
|
|
11
11
|
require 'shaf/generator/base'
|
12
12
|
require 'shaf/generator/controller'
|
13
|
+
require 'shaf/generator/doc'
|
13
14
|
require 'shaf/generator/forms'
|
14
15
|
require 'shaf/generator/migration'
|
15
16
|
require 'shaf/generator/model'
|
16
17
|
require 'shaf/generator/policy'
|
17
18
|
require 'shaf/generator/scaffold'
|
18
19
|
require 'shaf/generator/serializer'
|
20
|
+
require 'shaf/generator/profile'
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'shaf/authenticator'
|
5
|
+
require 'shaf/errors'
|
6
|
+
|
7
|
+
module Shaf
|
8
|
+
module Authentication
|
9
|
+
class NoChallengesError < Error
|
10
|
+
def initialize(realm)
|
11
|
+
super("No Authentication challenges for realm: #{realm.inspect}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class RealmChangedError < Error
|
16
|
+
def initialize(from:, to:)
|
17
|
+
super <<~ERR
|
18
|
+
Realm was changed from "#{from}" to "#{to}". This is not allowed!
|
19
|
+
Each request corresponds to a certain realm and cannot be changed.
|
20
|
+
This is probably caused by a call to `current_user` using the
|
21
|
+
default realm (from `Shaf::Settings.default_authentication_realm`)
|
22
|
+
and then using `#authenticate realm: 'some_other_realm'
|
23
|
+
ERR
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def www_authenticate(realm: Settings.default_authentication_realm)
|
28
|
+
challenges = Authenticator.challenges_for(realm: realm)
|
29
|
+
raise NoChallengesError.new(realm) if challenges.empty?
|
30
|
+
|
31
|
+
headers 'WWW-Authenticate' => challenges.map(&:to_s)
|
32
|
+
end
|
33
|
+
|
34
|
+
def authenticate(realm: Settings.default_authentication_realm)
|
35
|
+
if defined?(@current_realm) && @current_realm&.to_s != realm&.to_s
|
36
|
+
raise RealmChangedError.new(from: @current_realm , to: realm)
|
37
|
+
else
|
38
|
+
@current_realm = realm
|
39
|
+
end
|
40
|
+
|
41
|
+
current_user.tap do |user|
|
42
|
+
www_authenticate(realm: realm) unless user
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def authenticate!(realm: Settings.default_authentication_realm)
|
47
|
+
user = authenticate(realm: realm)
|
48
|
+
return user if user
|
49
|
+
|
50
|
+
msg = +"Unauthorized action"
|
51
|
+
msg << " (Realm: #{realm})" if realm
|
52
|
+
raise Shaf::Errors::UnauthorizedError, msg
|
53
|
+
end
|
54
|
+
alias current_user! authenticate!
|
55
|
+
|
56
|
+
def authenticated?
|
57
|
+
!current_user.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
def current_user
|
61
|
+
unless defined? @current_realm
|
62
|
+
if Settings.key? :default_authentication_realm
|
63
|
+
@current_realm = Settings.default_authentication_realm
|
64
|
+
else
|
65
|
+
Shaf.logger.info <<~MSG
|
66
|
+
No realm has been provided!
|
67
|
+
Authentication/authorization cannot be performed. Did you perhaps
|
68
|
+
forget to configure a realm in
|
69
|
+
`Settings.default_authentication_realm` or provide it when calling
|
70
|
+
`#authenticate!` (or `#authenticate!`)
|
71
|
+
MSG
|
72
|
+
return
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
@current_user ||= Authenticator.user(request.env, realm: @current_realm)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/shaf/helpers/payload.rb
CHANGED
@@ -2,50 +2,32 @@
|
|
2
2
|
|
3
3
|
require 'set'
|
4
4
|
require 'shaf/responder'
|
5
|
+
require 'shaf/parser'
|
6
|
+
require 'shaf/errors'
|
5
7
|
|
6
8
|
module Shaf
|
7
9
|
module Payload
|
8
|
-
EXCLUDED_FORM_PARAMS = ['captures', 'splat'].freeze
|
9
10
|
NO_VALUE = Object.new.freeze
|
10
11
|
|
11
12
|
private
|
12
13
|
|
13
14
|
def payload
|
14
|
-
@payload
|
15
|
-
|
16
|
-
|
17
|
-
def read_input
|
18
|
-
request.body.rewind unless request.body.pos.zero?
|
19
|
-
request.body.read
|
20
|
-
ensure
|
21
|
-
request.body.rewind
|
15
|
+
return @payload if defined? @payload
|
16
|
+
@payload = parse_payload
|
22
17
|
end
|
23
18
|
|
24
19
|
def parse_payload
|
25
|
-
|
26
|
-
return params.reject { |key, _| EXCLUDED_FORM_PARAMS.include? key }
|
27
|
-
end
|
20
|
+
return unless Parser.input? request
|
28
21
|
|
29
|
-
|
30
|
-
|
22
|
+
parser = Parser.for(request)
|
23
|
+
raise Errors::UnsupportedMediaTypeError.new(request: request) unless parser
|
31
24
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
rescue Errors::UnsupportedMediaTypeError
|
36
|
-
raise
|
37
|
-
rescue StandardError => e
|
25
|
+
log.debug "Parsing input using: #{parser.class}"
|
26
|
+
parser.call
|
27
|
+
rescue Parser::Error => e
|
38
28
|
raise Errors::BadRequestError, "Failed to parse input payload: #{e.message}"
|
39
29
|
end
|
40
30
|
|
41
|
-
def suported_media_type?
|
42
|
-
request.env['CONTENT_TYPE'].match? %r{\Aapplication/(hal\+)?json}
|
43
|
-
end
|
44
|
-
|
45
|
-
def raise_unsupported_media_type_error(request)
|
46
|
-
raise Errors::UnsupportedMediaTypeError.new(request: request)
|
47
|
-
end
|
48
|
-
|
49
31
|
def safe_params(*fields)
|
50
32
|
return {} unless payload
|
51
33
|
|
@@ -58,10 +40,6 @@ module Shaf
|
|
58
40
|
end
|
59
41
|
end
|
60
42
|
|
61
|
-
def ignore_form_input?(name)
|
62
|
-
name == '_method'
|
63
|
-
end
|
64
|
-
|
65
43
|
def profile(value = NO_VALUE)
|
66
44
|
return @profile if value == NO_VALUE
|
67
45
|
@profile = value
|
@@ -86,15 +64,17 @@ module Shaf
|
|
86
64
|
kwargs[:profile] ||= profile
|
87
65
|
|
88
66
|
log.info "#{request.request_method} #{request.path_info} => #{status}"
|
89
|
-
|
67
|
+
responder = Responder.for(request, resource)
|
68
|
+
payload = responder.call(self, resource, preload: preload, **kwargs)
|
90
69
|
add_cache_headers(payload, kwargs)
|
91
|
-
|
92
70
|
payload
|
93
71
|
rescue StandardError => err
|
94
72
|
log.error "Failure: #{err.message}\n#{err.backtrace}"
|
95
73
|
if status == 500
|
96
74
|
content_type mime_type(:json)
|
97
75
|
body JSON.generate(failure: err.message)
|
76
|
+
elsif err.is_a? Errors::ServerError
|
77
|
+
respond_with(err)
|
98
78
|
else
|
99
79
|
respond_with(Errors::ServerError.new(err.message))
|
100
80
|
end
|
data/lib/shaf/helpers.rb
CHANGED
@@ -3,6 +3,8 @@ require 'shaf/helpers/json_html'
|
|
3
3
|
require 'shaf/helpers/paginate'
|
4
4
|
require 'shaf/helpers/payload'
|
5
5
|
require 'shaf/helpers/http_header'
|
6
|
+
require 'shaf/helpers/authentication'
|
7
|
+
require 'shaf/helpers/vary'
|
6
8
|
|
7
9
|
module Shaf
|
8
10
|
def self.helpers
|
@@ -12,6 +14,8 @@ module Shaf
|
|
12
14
|
Paginate,
|
13
15
|
Payload,
|
14
16
|
HttpHeader,
|
17
|
+
Authentication,
|
18
|
+
Vary,
|
15
19
|
]
|
16
20
|
end
|
17
21
|
end
|
data/lib/shaf/logger.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Parser
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
def inherited(child)
|
6
|
+
Parser.register(child)
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def mime_type(type = nil, value = nil)
|
11
|
+
if type
|
12
|
+
@mime_type = type
|
13
|
+
@mime_type = Sinatra::Base.mime_type(type, value) if type.is_a? Symbol
|
14
|
+
end
|
15
|
+
|
16
|
+
@mime_type if defined? @mime_type
|
17
|
+
end
|
18
|
+
|
19
|
+
def can_handle?(request)
|
20
|
+
mime_type == request.content_type
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :request, :body
|
26
|
+
|
27
|
+
def initialize(request:, body:)
|
28
|
+
@request = request
|
29
|
+
@body = body
|
30
|
+
end
|
31
|
+
|
32
|
+
def call
|
33
|
+
raise NotImplementedError, "#{self.class} must implement #call"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def mime_type
|
39
|
+
self.class.mime_type
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Parser
|
3
|
+
class FormData < Base
|
4
|
+
def self.can_handle?(request)
|
5
|
+
request.form_data? || request.parseable_data?
|
6
|
+
end
|
7
|
+
|
8
|
+
def call
|
9
|
+
request.POST.tap do |data| # Returns form params from Rack::Request
|
10
|
+
data.delete '_method' # If the method override hack is used remove the _method key
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Parser
|
3
|
+
class Json < Base
|
4
|
+
|
5
|
+
mime_type :json, 'application/json'
|
6
|
+
|
7
|
+
def self.can_handle?(request)
|
8
|
+
request.content_type&.match? %r{application/(.*\+)?json}
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
@payload ||= parse_json
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_json
|
18
|
+
return {} if body.empty?
|
19
|
+
|
20
|
+
JSON.parse(body, symbolize_names: true)
|
21
|
+
rescue JSON::ParserError => e
|
22
|
+
raise Error, e.message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/shaf/parser.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Shaf
|
6
|
+
module Parser
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
INPUT_BODY = 'shaf.input_body'
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def register(parser)
|
13
|
+
parsers << parser
|
14
|
+
end
|
15
|
+
|
16
|
+
def unregister(parser)
|
17
|
+
parsers.delete(parser)
|
18
|
+
end
|
19
|
+
|
20
|
+
def input?(request)
|
21
|
+
!!input(request)
|
22
|
+
end
|
23
|
+
|
24
|
+
def for(request)
|
25
|
+
clazz = parser_for(request)
|
26
|
+
return unless clazz
|
27
|
+
|
28
|
+
body = input(request)
|
29
|
+
clazz.new(request: request, body: body)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parser_for(request)
|
35
|
+
parsers.find do |parser|
|
36
|
+
parser.can_handle? request
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parsers
|
41
|
+
@parsers ||= Set.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def input(request)
|
45
|
+
body = request.get_header(INPUT_BODY)
|
46
|
+
body ||= read_input(request).tap do |b|
|
47
|
+
request.set_header(INPUT_BODY, b)
|
48
|
+
end
|
49
|
+
|
50
|
+
body unless String(body).strip.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_input(request)
|
54
|
+
request.body.rewind
|
55
|
+
request.body.read
|
56
|
+
ensure
|
57
|
+
request.body.rewind
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
require 'shaf/parser/base'
|
64
|
+
require 'shaf/parser/json'
|
65
|
+
require 'shaf/parser/form_data'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/unique_id'
|
4
|
+
|
5
|
+
module Shaf
|
6
|
+
class Profile
|
7
|
+
class Attribute
|
8
|
+
include UniqueId
|
9
|
+
|
10
|
+
attr_reader :name, :doc, :href, :type, :parent
|
11
|
+
|
12
|
+
def initialize(name, **opts)
|
13
|
+
@name = name.to_sym
|
14
|
+
@doc = opts[:doc].freeze
|
15
|
+
@href = opts[:href].freeze
|
16
|
+
@type = opts[:type]&.to_s
|
17
|
+
@parent = opts[:parent]
|
18
|
+
end
|
19
|
+
|
20
|
+
def attributes
|
21
|
+
@attributes ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def relations
|
25
|
+
@relations ||= []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/attribute'
|
4
|
+
require 'shaf/profile/relation'
|
5
|
+
|
6
|
+
module Shaf
|
7
|
+
class Profile
|
8
|
+
class Evaluator
|
9
|
+
attr_reader :parent, :allowed
|
10
|
+
|
11
|
+
def initialize(parent:, allowed: nil)
|
12
|
+
@parent = parent
|
13
|
+
@allowed = allowed && Array(allowed).map(&:to_sym)
|
14
|
+
end
|
15
|
+
|
16
|
+
def attribute(name, doc:, type: :string, &block)
|
17
|
+
return unless allow? :attribute
|
18
|
+
|
19
|
+
attr = Attribute.new(name, doc: doc, type: type, parent: parent)
|
20
|
+
self.class.new(parent: attr, allowed: allowed).instance_exec(&block) if block
|
21
|
+
parent.attributes << attr
|
22
|
+
end
|
23
|
+
|
24
|
+
def relation(name, **kwargs, &block)
|
25
|
+
return unless allow? :rel
|
26
|
+
|
27
|
+
rel = Relation.new(name, parent: parent, **kwargs)
|
28
|
+
self.class.new(parent: rel, allowed: [:attribute]).instance_exec(&block) if block
|
29
|
+
parent.relations << rel
|
30
|
+
end
|
31
|
+
alias rel relation
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def allow?(name)
|
36
|
+
return true unless allowed
|
37
|
+
return true if allowed.include? name
|
38
|
+
|
39
|
+
Shaf.log.warn "#{name} is not allowed to be nested inside #{parent.class} " \
|
40
|
+
"(or parent object containing #{parent.class})"
|
41
|
+
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'shaf/profile/unique_id'
|
4
|
+
|
5
|
+
module Shaf
|
6
|
+
class Profile
|
7
|
+
class Relation
|
8
|
+
include UniqueId
|
9
|
+
|
10
|
+
attr_reader :name, :doc, :href, :http_methods, :payload_type, :content_type, :parent
|
11
|
+
|
12
|
+
def initialize(name, **opts)
|
13
|
+
@name = name.to_sym
|
14
|
+
@doc = opts[:doc].freeze
|
15
|
+
@href = opts[:href].freeze
|
16
|
+
http_methods = Array(opts[:http_method]) + Array(opts[:http_methods])
|
17
|
+
http_methods << 'GET' if http_methods.empty?
|
18
|
+
@http_methods = http_methods.map { |m| m.to_s.upcase }.uniq.freeze
|
19
|
+
@payload_type = opts[:payload_type].freeze
|
20
|
+
@content_type = opts[:content_type].freeze
|
21
|
+
@parent = opts[:parent]
|
22
|
+
end
|
23
|
+
|
24
|
+
def attributes
|
25
|
+
@attributes ||= []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|