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
data/lib/shaf/responder/hal.rb
CHANGED
@@ -1,24 +1,85 @@
|
|
1
|
-
require 'shaf/responder/hal_serializable'
|
2
|
-
|
3
1
|
module Shaf
|
4
2
|
module Responder
|
5
3
|
class Hal < Base
|
6
|
-
include HalSerializable
|
7
|
-
|
8
4
|
use_as_default!
|
9
5
|
mime_type :hal, 'application/hal+json'
|
10
6
|
|
7
|
+
def self.can_handle?(resource)
|
8
|
+
return false if resource.is_a? StandardError
|
9
|
+
|
10
|
+
if resource.is_a? Class
|
11
|
+
return false if resource <= Shaf::Profile
|
12
|
+
end
|
13
|
+
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
11
17
|
def body
|
12
18
|
@body ||= generate_json
|
13
19
|
end
|
14
20
|
|
21
|
+
def lookup_rel(rel, response)
|
22
|
+
hal = response.serialized_hash
|
23
|
+
links = hal&.dig(:_links, rel.to_sym)
|
24
|
+
return [] unless links
|
25
|
+
|
26
|
+
links = [links] unless links.is_a? Array
|
27
|
+
links.map do |link|
|
28
|
+
{
|
29
|
+
href: link[:href],
|
30
|
+
as: 'fetch',
|
31
|
+
crossorigin: 'anonymous'
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
15
35
|
private
|
16
36
|
|
17
37
|
def mime_type
|
18
38
|
type = super
|
19
|
-
type = "#{type};profile
|
39
|
+
type = "#{type}; profile=\"#{profile}\"" if profile
|
20
40
|
type
|
21
41
|
end
|
42
|
+
|
43
|
+
def collection?
|
44
|
+
!!options.fetch(:collection, false)
|
45
|
+
end
|
46
|
+
|
47
|
+
def serializer
|
48
|
+
@serializer ||= options[:serializer] || HALPresenter.lookup_presenter(resource)
|
49
|
+
end
|
50
|
+
|
51
|
+
def serialized_hash
|
52
|
+
raise Errors::NotAcceptableError unless serializer
|
53
|
+
|
54
|
+
@serialized_hash ||=
|
55
|
+
if collection?
|
56
|
+
serializer.to_collection(resource, current_user: user, as_hash: true, **options)
|
57
|
+
else
|
58
|
+
serializer.to_hal(resource, current_user: user, as_hash: true, **options)
|
59
|
+
end
|
60
|
+
|
61
|
+
# hal_presenter versions before v1.5.0 does not understand the :as_hash
|
62
|
+
# keyword argument and will always return a String from
|
63
|
+
# to_hal/to_collection, thus we need to parse it if its a String.
|
64
|
+
if @serialized_hash.is_a? String
|
65
|
+
@body = @serialized_hash
|
66
|
+
@serialized_hash = JSON.parse(@serialized_hash, symbolize_names: true)
|
67
|
+
end
|
68
|
+
|
69
|
+
@serialized_hash
|
70
|
+
end
|
71
|
+
|
72
|
+
def profile
|
73
|
+
@profile ||= options[:profile]
|
74
|
+
return unless @profile || serializer
|
75
|
+
|
76
|
+
@profile ||= serializer.semantic_profile
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_json
|
80
|
+
# FIXME: change to Oj??
|
81
|
+
JSON.generate(serialized_hash)
|
82
|
+
end
|
22
83
|
end
|
23
84
|
end
|
24
85
|
end
|
data/lib/shaf/responder/html.rb
CHANGED
@@ -1,14 +1,50 @@
|
|
1
|
-
require 'shaf/responder/hal_serializable'
|
2
|
-
|
3
1
|
module Shaf
|
4
2
|
module Responder
|
5
3
|
class Html < Base
|
6
|
-
include HalSerializable
|
7
|
-
|
8
4
|
mime_type :html
|
9
5
|
|
6
|
+
class << self
|
7
|
+
def call(controller, resource, preload: [], **kwargs)
|
8
|
+
responder = responder_for(resource, controller, preload_rels: preload, **kwargs)
|
9
|
+
response = responder.build_response
|
10
|
+
add_preload_links(controller, response)
|
11
|
+
|
12
|
+
html_responder = new(controller, resource, response: response)
|
13
|
+
html_response = html_responder.build_response
|
14
|
+
log_response(controller, response)
|
15
|
+
|
16
|
+
write_response(controller, html_response)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Returns the "original" (non-html) responder
|
21
|
+
def responder_for(resource, controller, **kwargs)
|
22
|
+
responders = Responder.send(:supported_responders_for, resource)
|
23
|
+
responder_class = (responders - [self]).first || Responder.default
|
24
|
+
responder_class.new(controller, resource, **kwargs)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
10
28
|
def body
|
11
|
-
|
29
|
+
response = options[:response]
|
30
|
+
serialized = response.serialized_hash
|
31
|
+
if serialized.empty?
|
32
|
+
serialized = begin
|
33
|
+
JSON.parse(response.body)
|
34
|
+
rescue StandardError
|
35
|
+
response.body
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
render serialized
|
40
|
+
end
|
41
|
+
|
42
|
+
def render(serialized)
|
43
|
+
locals = {
|
44
|
+
request_headers: request_headers,
|
45
|
+
response_headers: response_headers,
|
46
|
+
serialized: serialized
|
47
|
+
}
|
12
48
|
|
13
49
|
template =
|
14
50
|
case resource
|
@@ -22,25 +58,16 @@ module Shaf
|
|
22
58
|
controller.erb(template, locals: locals)
|
23
59
|
end
|
24
60
|
|
25
|
-
def variables
|
26
|
-
{
|
27
|
-
request_headers: request_headers,
|
28
|
-
response_headers: response_headers,
|
29
|
-
serialized: serialized_hash
|
30
|
-
}
|
31
|
-
end
|
32
|
-
|
33
61
|
def request_headers
|
34
62
|
controller.request_headers
|
35
63
|
end
|
36
64
|
|
37
65
|
def response_headers
|
38
|
-
etag, kind = controller.send(:etag_for,
|
66
|
+
etag, kind = controller.send(:etag_for, options[:response].body)
|
39
67
|
prefix = kind == :weak ? 'W/' : ''
|
40
68
|
etag = %Q{#{prefix}"#{etag}"}
|
41
69
|
|
42
|
-
type =
|
43
|
-
type = "#{type};profile=#{profile}" if profile
|
70
|
+
type = options[:response].content_type
|
44
71
|
|
45
72
|
controller.headers.merge('Content-Type' => type, 'ETag' => etag)
|
46
73
|
end
|
@@ -4,7 +4,7 @@ module Shaf
|
|
4
4
|
mime_type :problem_json, 'application/problem+json'
|
5
5
|
|
6
6
|
def self.can_handle?(resource)
|
7
|
-
klass = resource.is_a?(Class) ? resource: resource.class
|
7
|
+
klass = resource.is_a?(Class) ? resource : resource.class
|
8
8
|
klass <= StandardError
|
9
9
|
end
|
10
10
|
|
@@ -16,7 +16,7 @@ module Shaf
|
|
16
16
|
|
17
17
|
def hash
|
18
18
|
{
|
19
|
-
status:
|
19
|
+
status: status,
|
20
20
|
type: code,
|
21
21
|
title: title,
|
22
22
|
detail: resource.message,
|
data/lib/shaf/responder.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'set'
|
2
|
+
require 'shaf/errors'
|
2
3
|
|
3
4
|
module Shaf
|
4
5
|
module Responder
|
6
|
+
MEDIA_TYPE_SUFFIX_PATTERN = %r{(.*)/([^+]+)\+(.*)}.freeze
|
7
|
+
|
5
8
|
class << self
|
6
9
|
def register(responder)
|
7
10
|
uninitialized << responder
|
@@ -15,15 +18,23 @@ module Shaf
|
|
15
18
|
end
|
16
19
|
|
17
20
|
def for(request, resource)
|
21
|
+
return Responder::ProblemJson if resource.is_a?(Errors::NotAcceptableError)
|
22
|
+
|
18
23
|
types = supported_responders_for(resource).map(&:mime_type)
|
19
|
-
|
20
|
-
|
24
|
+
types = move_html_to_last(types)
|
25
|
+
|
26
|
+
mime = preferred_type(request, types)
|
27
|
+
responders[mime] or raise Errors::NotAcceptableError
|
21
28
|
end
|
22
29
|
|
23
30
|
def default=(responder)
|
24
31
|
responders.default = responder
|
25
32
|
end
|
26
33
|
|
34
|
+
def default
|
35
|
+
responders.default
|
36
|
+
end
|
37
|
+
|
27
38
|
private
|
28
39
|
|
29
40
|
def supported_responders_for(resource)
|
@@ -41,6 +52,23 @@ module Shaf
|
|
41
52
|
@supported_responders ||= Hash.new { |hash, key| hash[key] = Set.new }
|
42
53
|
end
|
43
54
|
|
55
|
+
def preferred_type(request, types)
|
56
|
+
mime = request.preferred_type(types)
|
57
|
+
return mime if mime
|
58
|
+
|
59
|
+
request.accept.find do |accept|
|
60
|
+
next if accept.match? MEDIA_TYPE_SUFFIX_PATTERN
|
61
|
+
|
62
|
+
types.find do |type|
|
63
|
+
m = type.match MEDIA_TYPE_SUFFIX_PATTERN
|
64
|
+
next unless m
|
65
|
+
|
66
|
+
format = "#{m[1]}/#{m[3]}"
|
67
|
+
return type if accept.to_str == format
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
44
72
|
def uninitialized
|
45
73
|
@uninitialized ||= []
|
46
74
|
end
|
@@ -57,6 +85,16 @@ module Shaf
|
|
57
85
|
init_responders!
|
58
86
|
end
|
59
87
|
end
|
88
|
+
|
89
|
+
# We want to always be able to respond with text/html, but only when
|
90
|
+
# asked for (Accept header) to be able to let other more specific mime
|
91
|
+
# types take precedence we need to move text/html to the end of the
|
92
|
+
# array.
|
93
|
+
def move_html_to_last(types)
|
94
|
+
return types unless types.include? Html.mime_type
|
95
|
+
|
96
|
+
(types - [Html.mime_type]) << Html.mime_type
|
97
|
+
end
|
60
98
|
end
|
61
99
|
end
|
62
100
|
end
|
@@ -65,3 +103,4 @@ require 'shaf/responder/base'
|
|
65
103
|
require 'shaf/responder/hal'
|
66
104
|
require 'shaf/responder/html'
|
67
105
|
require 'shaf/responder/problem_json'
|
106
|
+
require 'shaf/responder/alps_json'
|
data/lib/shaf/router.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'shaf/middleware'
|
4
3
|
require 'set'
|
4
|
+
require 'sinatra'
|
5
|
+
require 'shaf/errors'
|
5
6
|
|
6
7
|
module Shaf
|
7
8
|
class Router
|
@@ -21,16 +22,56 @@ module Shaf
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
25
|
+
class NullController
|
26
|
+
def call(env)
|
27
|
+
request = request(env)
|
28
|
+
responder = Responder.for(request, error)
|
29
|
+
responder.call(self, error)
|
30
|
+
|
31
|
+
response.finish
|
32
|
+
end
|
33
|
+
|
34
|
+
# Called from responder
|
35
|
+
def content_type(mime)
|
36
|
+
response["Content-Type"] = mime
|
37
|
+
end
|
38
|
+
|
39
|
+
# Called from responder
|
40
|
+
def body(body)
|
41
|
+
response.body = body
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def status
|
47
|
+
500
|
48
|
+
end
|
49
|
+
|
50
|
+
def request(env)
|
51
|
+
Sinatra::Request.new(env)
|
52
|
+
end
|
53
|
+
|
54
|
+
def response
|
55
|
+
@response ||= Sinatra::Response.new(nil, status)
|
56
|
+
end
|
57
|
+
|
58
|
+
def error
|
59
|
+
@error ||= Errors::ServerError.new(
|
60
|
+
'Internal error: No controller has been mounted on Router',
|
61
|
+
code: 'NO_MOUNTED_CONTROLLERS',
|
62
|
+
title: 'Shaf::Router must have at least one mounted controller',
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
24
67
|
class << self
|
25
68
|
def mount(controller, default: false)
|
26
69
|
@default_controller = controller if default
|
27
|
-
|
28
|
-
@controllers << controller
|
70
|
+
controllers << controller
|
29
71
|
end
|
30
72
|
|
31
73
|
def routes
|
32
|
-
|
33
|
-
@routes
|
74
|
+
@routes ||= init_routes
|
34
75
|
end
|
35
76
|
|
36
77
|
# This controller will be used when no other can handle the request
|
@@ -41,32 +82,44 @@ module Shaf
|
|
41
82
|
|
42
83
|
private
|
43
84
|
|
44
|
-
|
85
|
+
def controllers
|
86
|
+
@controllers ||= []
|
87
|
+
end
|
45
88
|
|
46
89
|
def init_routes
|
47
|
-
|
90
|
+
routes = Hash.new do |hash, key|
|
48
91
|
hash[key] = Hash.new { |h, k| h[k] = Set.new }
|
49
92
|
end
|
50
|
-
controllers.each { |controller|
|
93
|
+
controllers.each { |controller| init(controller, routes) }
|
94
|
+
routes
|
51
95
|
end
|
52
96
|
|
53
|
-
def
|
97
|
+
def init(controller, routes)
|
54
98
|
controller.routes.each do |method, controller_routes|
|
55
99
|
routes[method][controller] += controller_routes.map(&:first)
|
56
100
|
end
|
57
101
|
end
|
58
102
|
end
|
59
103
|
|
60
|
-
def initialize(app)
|
104
|
+
def initialize(app = NullController.new)
|
61
105
|
@app = app
|
62
106
|
end
|
63
107
|
|
64
108
|
def call(env)
|
109
|
+
# When the api is mounted in Rails then the mount point will be not be
|
110
|
+
# present in PATH_INFO but it will instead be available in SCRIPT_NAME
|
111
|
+
# Shaf need to know about the full path in order to make all path helpers
|
112
|
+
# work, so we need to add the mountpoint back to PATH_INFO.
|
113
|
+
unless String(env['SCRIPT_NAME']).empty?
|
114
|
+
env['PATH_INFO'] = '' if env['PATH_INFO'] == '/'
|
115
|
+
env['PATH_INFO'] = "#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
|
116
|
+
end
|
117
|
+
|
65
118
|
http_method, path = http_details(env)
|
66
119
|
|
67
120
|
result = nil
|
68
121
|
|
69
|
-
|
122
|
+
each_controller_for(http_method, path) do |controller|
|
70
123
|
result = controller.call(env)
|
71
124
|
break unless cascade? result
|
72
125
|
end
|
@@ -80,7 +133,7 @@ module Shaf
|
|
80
133
|
[env['REQUEST_METHOD'], env['PATH_INFO']]
|
81
134
|
end
|
82
135
|
|
83
|
-
def
|
136
|
+
def each_controller_for(http_method, path)
|
84
137
|
find_cached(http_method, path).each { |ctrlr| yield ctrlr }
|
85
138
|
|
86
139
|
if controller = find(http_method, path)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hal_presenter'
|
4
|
+
|
5
|
+
module Shaf
|
6
|
+
# A base class used for serializing objects into a HAL representations.
|
7
|
+
class Serializer
|
8
|
+
extend HALPresenter
|
9
|
+
extend UriHelper
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_reader :default_curie_prefix
|
13
|
+
|
14
|
+
# Creates a link with rel profile and href pointing to the corresponding profile.
|
15
|
+
# It also adds a Curie link.
|
16
|
+
# @param name [String] the name of the profile
|
17
|
+
# @param curie_prefix [Symbol] the prefix used for the Curie
|
18
|
+
def profile(name, curie_prefix: :doc)
|
19
|
+
profile = Profiles.find name
|
20
|
+
|
21
|
+
super { profile&.urn || profile_uri(name) }
|
22
|
+
|
23
|
+
link :profile do
|
24
|
+
profile_uri(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
curie curie_prefix do
|
28
|
+
doc_curie_uri(name)
|
29
|
+
end
|
30
|
+
|
31
|
+
@default_curie_prefix = curie_prefix.to_sym
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/shaf/settings.rb
CHANGED
@@ -12,24 +12,41 @@ module Shaf
|
|
12
12
|
documents_dir: 'doc/api',
|
13
13
|
migrations_dir: 'db/migrations',
|
14
14
|
fixtures_dir: 'spec/fixtures',
|
15
|
-
auth_token_header: 'X-Auth-Token',
|
16
15
|
paginate_per_page: 25
|
17
16
|
}.freeze
|
18
17
|
|
19
18
|
class << self
|
20
|
-
def
|
21
|
-
|
22
|
-
config = Utils.read_config(SETTINGS_FILE, erb: true)
|
23
|
-
@settings.merge! config.fetch(env, {})
|
19
|
+
def env
|
20
|
+
(ENV['APP_ENV'] || ENV['RACK_ENV'] || 'development').to_sym
|
24
21
|
end
|
25
22
|
|
23
|
+
def key?(key)
|
24
|
+
settings.key? key
|
25
|
+
end
|
26
26
|
|
27
|
-
def
|
28
|
-
|
27
|
+
def to_h
|
28
|
+
settings.dup
|
29
|
+
end
|
30
|
+
|
31
|
+
def loaded?
|
32
|
+
!!defined? @settings
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def settings
|
38
|
+
load_config unless loaded?
|
39
|
+
@settings
|
40
|
+
end
|
41
|
+
|
42
|
+
def load_config
|
43
|
+
@settings = DEFAULTS.dup
|
44
|
+
config = Utils.read_config(SETTINGS_FILE, erb: true)
|
45
|
+
@settings.merge! config.fetch(env, {})
|
29
46
|
end
|
30
47
|
|
31
48
|
def method_missing(method, *args)
|
32
|
-
|
49
|
+
load_config unless loaded?
|
33
50
|
|
34
51
|
if method.to_s.end_with? '='
|
35
52
|
define_setter(method)
|
@@ -56,10 +73,6 @@ module Shaf
|
|
56
73
|
@settings[key] = arg
|
57
74
|
end
|
58
75
|
end
|
59
|
-
|
60
|
-
def to_h
|
61
|
-
@settings.dup
|
62
|
-
end
|
63
76
|
end
|
64
77
|
end
|
65
78
|
end
|
data/lib/shaf/spec/base.rb
CHANGED
@@ -8,13 +8,18 @@ module Shaf
|
|
8
8
|
|
9
9
|
register_spec_type self do |_desc, args|
|
10
10
|
next unless args&.is_a?(Hash)
|
11
|
-
args[:type]
|
11
|
+
args[:type].to_s == 'integration'
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_accessor :__authenticated_user_id
|
17
|
+
|
18
|
+
def set_authentication
|
19
|
+
id = __authenticated_user_id
|
20
|
+
authorization = "#{Authenticator.scheme} #{id}" if id
|
21
|
+
|
22
|
+
header 'Authorization', authorization
|
18
23
|
end
|
19
24
|
|
20
25
|
def parse_response(body)
|
@@ -25,7 +30,22 @@ module Shaf
|
|
25
30
|
end
|
26
31
|
|
27
32
|
def app
|
28
|
-
App.
|
33
|
+
App.instance
|
34
|
+
end
|
35
|
+
|
36
|
+
def authenticate(user)
|
37
|
+
self.__authenticated_user_id = user&.id
|
38
|
+
end
|
39
|
+
|
40
|
+
def unauthenticate
|
41
|
+
self.__authenticated_user_id = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def with_authenticated(user, &block)
|
45
|
+
authenticate(user)
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
unauthenticate
|
29
49
|
end
|
30
50
|
|
31
51
|
def follow_rel(rel, method: nil)
|
@@ -37,14 +57,6 @@ module Shaf
|
|
37
57
|
get link[:href]
|
38
58
|
end
|
39
59
|
end
|
40
|
-
|
41
|
-
def auth_token(token)
|
42
|
-
@__integration_test_auth_token = token
|
43
|
-
end
|
44
|
-
|
45
|
-
def clear_auth_token
|
46
|
-
@__integration_test_auth_token = nil
|
47
|
-
end
|
48
60
|
end
|
49
61
|
end
|
50
62
|
end
|
@@ -29,11 +29,11 @@ module Shaf
|
|
29
29
|
last_payload[:_embedded]&.keys || []
|
30
30
|
end
|
31
31
|
|
32
|
-
def embedded(name = nil)
|
32
|
+
def embedded(name = nil, &block)
|
33
33
|
assert_has_embedded name unless name.nil?
|
34
34
|
keys = [:_embedded, name&.to_sym].compact
|
35
35
|
return last_payload.dig(*keys) unless block_given?
|
36
|
-
exec_embed_block(last_payload.dig(*keys),
|
36
|
+
exec_embed_block(last_payload.dig(*keys), block)
|
37
37
|
end
|
38
38
|
|
39
39
|
def each_embedded(name, &block)
|
data/lib/shaf/spec.rb
CHANGED
@@ -1,29 +1,26 @@
|
|
1
1
|
module Shaf
|
2
2
|
module Tasks
|
3
3
|
class RoutesTask
|
4
|
-
|
4
|
+
extend Rake::DSL
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require 'config/database'
|
6
|
+
desc 'List path helpers'
|
7
|
+
task :routes do
|
8
|
+
require 'shaf/utils'
|
9
|
+
require 'config/database'
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
extend Shaf::Utils
|
12
|
+
bootstrap
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
Shaf::ApiRoutes::Registry.controllers.each do |controller|
|
15
|
+
puts "\n#{controller}:"
|
16
|
+
Shaf::ApiRoutes::Registry.routes_for(controller) do |methods, template, symbol|
|
17
|
+
puts format(
|
18
|
+
' %-50<symbol>s%-30<methods>s%<template>s',
|
19
|
+
{symbol: symbol, methods: methods.join(' | '), template: template}
|
20
|
+
)
|
22
21
|
end
|
23
22
|
end
|
24
23
|
end
|
25
24
|
end
|
26
|
-
|
27
|
-
RoutesTask.new
|
28
25
|
end
|
29
26
|
end
|