shaf 2.1.0 → 3.0.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/shaf/app.rb +27 -9
  4. data/lib/shaf/command/base.rb +4 -0
  5. data/lib/shaf/command/new.rb +5 -1
  6. data/lib/shaf/command/server.rb +5 -1
  7. data/lib/shaf/command/templates/config/settings.yml.erb +9 -8
  8. data/lib/shaf/extensions/resource_uris.rb +37 -155
  9. data/lib/shaf/extensions/symbolic_routes.rb +5 -18
  10. data/lib/shaf/formable/builder.rb +58 -19
  11. data/lib/shaf/formable/form.rb +13 -10
  12. data/lib/shaf/formable.rb +10 -23
  13. data/lib/shaf/generator/base.rb +82 -0
  14. data/lib/shaf/generator/controller.rb +19 -35
  15. data/lib/shaf/generator/forms.rb +10 -14
  16. data/lib/shaf/generator/migration/add_column.rb +0 -4
  17. data/lib/shaf/generator/migration/add_index.rb +0 -4
  18. data/lib/shaf/generator/migration/base.rb +8 -0
  19. data/lib/shaf/generator/migration/create_table.rb +0 -4
  20. data/lib/shaf/generator/migration/drop_column.rb +0 -4
  21. data/lib/shaf/generator/migration/rename_column.rb +0 -4
  22. data/lib/shaf/generator/model.rb +29 -14
  23. data/lib/shaf/generator/policy.rb +8 -14
  24. data/lib/shaf/generator/profile.rb +9 -14
  25. data/lib/shaf/generator/scaffold.rb +6 -9
  26. data/lib/shaf/generator/serializer.rb +31 -30
  27. data/lib/shaf/generator/templates/api/controller.rb.erb +13 -13
  28. data/lib/shaf/generator/templates/api/forms.rb.erb +2 -2
  29. data/lib/shaf/generator/templates/api/model.rb.erb +1 -1
  30. data/lib/shaf/generator/templates/api/profile.rb.erb +1 -1
  31. data/lib/shaf/generator/templates/api/serializer.rb.erb +1 -1
  32. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +14 -14
  33. data/lib/shaf/helpers/paginate.rb +1 -1
  34. data/lib/shaf/profile.rb +8 -8
  35. data/lib/shaf/registrable_factory.rb +62 -32
  36. data/lib/shaf/responder/problem_json.rb +1 -1
  37. data/lib/shaf/router.rb +65 -12
  38. data/lib/shaf/spec/integration_spec.rb +1 -1
  39. data/lib/shaf/tasks.rb +0 -1
  40. data/lib/shaf/upgrade/package.rb +5 -7
  41. data/lib/shaf/version.rb +1 -1
  42. data/templates/Rakefile +0 -6
  43. data/templates/api/controllers/base_controller.rb +0 -2
  44. data/templates/api/serializers/root_serializer.rb +0 -11
  45. data/templates/config/initializers/middleware.rb +6 -0
  46. data/templates/spec/spec_helper.rb +4 -1
  47. data/upgrades/3.0.0.tar.gz +0 -0
  48. data.tar.gz.sig +0 -0
  49. metadata +19 -22
  50. metadata.gz.sig +0 -0
  51. data/lib/shaf/api_doc/comment.rb +0 -27
  52. data/lib/shaf/api_doc/document.rb +0 -137
  53. data/lib/shaf/api_doc/link_relations.rb +0 -77
  54. data/lib/shaf/middleware.rb +0 -1
  55. data/lib/shaf/tasks/api_doc_task.rb +0 -146
@@ -11,19 +11,8 @@ module Shaf
11
11
  create_profile
12
12
  end
13
13
 
14
- def name
15
- n = args.first || ""
16
- return n unless n.empty?
17
- raise Command::ArgumentError,
18
- "Please provide a model name when using the serializer generator!"
19
- end
20
-
21
- def plural_name
22
- Utils.pluralize(name)
23
- end
24
-
25
- def model_class_name
26
- Utils.model_name(name)
14
+ def serializer_class_name
15
+ "#{model_class_name}Serializer"
27
16
  end
28
17
 
29
18
  def policy_class_name
@@ -38,21 +27,31 @@ module Shaf
38
27
  'spec/serializer_spec.rb'
39
28
  end
40
29
 
41
- def target
42
- "api/serializers/#{name}_serializer.rb"
30
+ def target_dir
31
+ 'api/serializers'
32
+ end
33
+
34
+ def target_name
35
+ "#{name}_serializer.rb"
43
36
  end
44
37
 
45
38
  def spec_target
46
- "spec/serializers/#{name}_serializer_spec.rb"
39
+ target(directory: 'spec/serializers', name: "#{name}_serializer_spec.rb")
40
+ end
41
+
42
+ def policy_file
43
+ File.join(['policies', namespace, "#{name}_policy"].compact)
47
44
  end
48
45
 
49
46
  def create_serializer
50
47
  content = render(template, opts)
48
+ content = wrap_in_module(content, module_name)
51
49
  write_output(target, content)
52
50
  end
53
51
 
54
52
  def create_serializer_spec
55
53
  content = render(spec_template, opts)
54
+ content = wrap_in_module(content, module_name, search: "describe #{serializer_class_name}")
56
55
  write_output(spec_target, content)
57
56
  end
58
57
 
@@ -74,16 +73,16 @@ module Shaf
74
73
 
75
74
  def profile_with_doc
76
75
  doc = <<~DOC
77
- # Adds a link to the '#{name}' profile and a curie. By default the
76
+ # Adds a link to the '#{resource_name}' profile and a curie. By default the
78
77
  # curie prefix is 'doc', use the `curie_prefix` keyword argument to
79
78
  # change this.
80
79
  # Note: the target of the profile link and the curie will be set to
81
- # `profile_uri('#{name}')` resp. `doc_curie_uri('#{name}')`. To
80
+ # `profile_uri('#{resource_name}')` resp. `doc_curie_uri('#{resource_name}')`. To
82
81
  # create links for external profiles or curies, delete the next line
83
82
  # and use `::link` and/or `::curie` instead.
84
83
  DOC
85
84
 
86
- doc.split("\n") << %Q(profile #{Utils.symbol_string(name)})
85
+ doc.split("\n") << %Q(profile #{Utils.symbol_string(resource_name)})
87
86
  end
88
87
 
89
88
  def links_with_doc
@@ -98,7 +97,7 @@ module Shaf
98
97
  def collection_link
99
98
  link(
100
99
  rel: "collection",
101
- uri_helper: "#{plural_name}_uri",
100
+ uri_helper: "#{collection_name}_uri",
102
101
  kwargs: {embed_depth: 0}
103
102
  )
104
103
  end
@@ -106,21 +105,21 @@ module Shaf
106
105
  def self_link
107
106
  link(
108
107
  rel: "self",
109
- uri_helper: "#{name}_uri(resource)"
108
+ uri_helper: "#{resource_name}_uri(resource)"
110
109
  )
111
110
  end
112
111
 
113
112
  def edit_link
114
113
  link(
115
114
  rel: "edit-form",
116
- uri_helper: "edit_#{name}_uri(resource)"
115
+ uri_helper: "edit_#{resource_name}_uri(resource)"
117
116
  )
118
117
  end
119
118
 
120
119
  def delete_link
121
120
  link(
122
121
  rel: "delete",
123
- uri_helper: "#{name}_uri(resource)",
122
+ uri_helper: "#{resource_name}_uri(resource)",
124
123
  kwargs: {curie: :doc}
125
124
  )
126
125
  end
@@ -128,7 +127,7 @@ module Shaf
128
127
  def create_link
129
128
  link(
130
129
  rel: "create-form",
131
- uri_helper: "new_#{name}_uri"
130
+ uri_helper: "new_#{resource_name}_uri"
132
131
  )
133
132
  end
134
133
 
@@ -147,9 +146,9 @@ module Shaf
147
146
  def collection_with_doc
148
147
  <<~EOS.split("\n")
149
148
  collection of: '#{plural_name}' do
150
- curie(:doc) { doc_curie_uri('#{name}') }
149
+ curie(:doc) { doc_curie_uri('#{resource_name}') }
151
150
 
152
- link :self, #{plural_name}_uri
151
+ link :self, #{collection_name}_uri
153
152
  link :up, root_uri
154
153
 
155
154
  #{create_link.join("\n ")}
@@ -160,10 +159,12 @@ module Shaf
160
159
  def opts
161
160
  {
162
161
  name: name,
163
- class_name: "#{model_class_name}Serializer",
162
+ resource_name: resource_name,
163
+ collection_name: collection_name,
164
+ class_name: serializer_class_name,
164
165
  model_class_name: model_class_name,
165
166
  policy_class_name: policy_class_name,
166
- policy_name: "#{name}_policy",
167
+ policy_file: policy_file,
167
168
  attribute_names: attribute_names,
168
169
  link_relations: link_relations,
169
170
  profile_with_doc: profile_with_doc,
@@ -174,12 +175,12 @@ module Shaf
174
175
  end
175
176
 
176
177
  def create_policy
177
- policy_args = ["policy", name, *attribute_names]
178
+ policy_args = ["policy", name_arg, *attribute_names]
178
179
  Generator::Factory.create(*policy_args, **options).call
179
180
  end
180
181
 
181
182
  def create_profile
182
- profile_args = ["profile", name, *attributes]
183
+ profile_args = ["profile", name_arg, *attributes]
183
184
  Generator::Factory.create(*profile_args, **options).call
184
185
  end
185
186
  end
@@ -4,45 +4,45 @@ class <%= controller_class_name %> < BaseController
4
4
 
5
5
  authorize_with <%= policy_class_name %>
6
6
 
7
- resource_uris_for :<%= name %>
7
+ resource_uris_for :<%= name %><%= namespace ? ", namespace: '#{namespace}'" : "" %>
8
8
 
9
- get :<%= plural_name %>_path do
9
+ get :<%= collection_name %>_path do
10
10
  authorize! :read
11
11
  collection = paginate(<%= model_class_name %>.order(:created_at).reverse)
12
12
  respond_with_collection collection, serializer: <%= serializer_class_name %>
13
13
  end
14
14
 
15
- get :new_<%= name %>_path do
15
+ get :new_<%= resource_name %>_path do
16
16
  authorize! :read
17
17
  cache_control(:private, http_cache_max_age: :short)
18
18
  respond_with create_form
19
19
  end
20
20
 
21
- post :<%= plural_name %>_path do
21
+ post :<%= collection_name %>_path do
22
22
  authorize! :write
23
23
  <%= name %> = <%= model_class_name %>.create(<%= name %>_params)
24
- headers('Location' => <%= name %>_uri(<%= name %>))
24
+ headers('Location' => <%= resource_name %>_uri(<%= name %>))
25
25
  respond_with <%= name %>, status: 201
26
26
  end
27
27
 
28
- get :<%= name %>_path do
28
+ get :<%= resource_name %>_path do
29
29
  authorize! :read
30
30
  respond_with <%= name %>
31
31
  end
32
32
 
33
- get :edit_<%= name %>_path do
33
+ get :edit_<%= resource_name %>_path do
34
34
  authorize! :write
35
35
  cache_control(:private, http_cache_max_age: :short)
36
36
  respond_with edit_form
37
37
  end
38
38
 
39
- put :<%= name %>_path do
39
+ put :<%= resource_name %>_path do
40
40
  authorize! :write
41
41
  <%= name %>.update(<%= name %>_params)
42
42
  respond_with <%= name %>
43
43
  end
44
44
 
45
- delete :<%= name %>_path do
45
+ delete :<%= resource_name %>_path do
46
46
  authorize! :write
47
47
  <%= name %>.destroy
48
48
  status 204
@@ -62,15 +62,15 @@ class <%= controller_class_name %> < BaseController
62
62
 
63
63
  def create_form
64
64
  <%= model_class_name %>.create_form.tap do |form|
65
- form.self_link = new_<%= name %>_uri
66
- form.href = <%= plural_name %>_uri
65
+ form.self_link = new_<%= resource_name %>_uri
66
+ form.href = <%= collection_name %>_uri
67
67
  end
68
68
  end
69
69
 
70
70
  def edit_form
71
71
  <%= name %>.edit_form.tap do |form|
72
- form.self_link = edit_<%= name %>_uri(<%= name %>)
73
- form.href = <%= name %>_uri(<%= name %>)
72
+ form.self_link = edit_<%= resource_name %>_uri(<%= name %>)
73
+ form.href = <%= resource_name %>_uri(<%= name %>)
74
74
  end
75
75
  end
76
76
  end
@@ -4,12 +4,12 @@ class <%= class_name %>
4
4
  forms_for(<%= model_class_name %>) do
5
5
  <%= fields.join("\n ") %>
6
6
 
7
- create do
7
+ create_form do
8
8
  title 'Create <%= model_class_name %>'
9
9
  name 'create-<%= model_name %>'
10
10
  end
11
11
 
12
- edit do
12
+ edit_form do
13
13
  instance_accessor
14
14
  title 'Update <%= model_class_name %>'
15
15
  name 'update-<%= model_name %>'
@@ -1,4 +1,4 @@
1
- class <%= class_name %> < Sequel::Model
1
+ class <%= class_name %> < Sequel::Model(:<%= table_name %>)
2
2
 
3
3
  end
4
4
 
@@ -1,6 +1,6 @@
1
1
  module Profiles
2
2
  class <%= profile_class_name %> < Shaf::Profile
3
- name '<%= profile_name %>'
3
+ name '<%= resource_name %>'
4
4
 
5
5
  doc 'FIXME: replace this with a description of the <%= profile_name %> profile'
6
6
 
@@ -1,5 +1,5 @@
1
1
  require 'serializers/base_serializer'
2
- require 'policies/<%= policy_name %>'
2
+ require '<%= policy_file %>'
3
3
 
4
4
  class <%= class_name %> < BaseSerializer
5
5
 
@@ -4,10 +4,10 @@ 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 <%= name %>_uri(<%= name %>)
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 <%= name %>_uri(<%= name %>)
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 -%>
@@ -16,10 +16,10 @@ describe <%= model_class_name %>, type: :integration do
16
16
  it "lists all <%= plural_name %>" do
17
17
  2.times { <%= model_class_name %>.create }
18
18
 
19
- get <%= plural_name %>_uri
19
+ get <%= collection_name %>_uri
20
20
  _(status).must_equal 200
21
21
  _(link_rels).must_include(:self)
22
- _(links[:self][:href]).must_include <%= plural_name %>_uri
22
+ _(links[:self][:href]).must_include <%= collection_name %>_uri
23
23
  _(embedded(:'<%= plural_name %>').size).must_equal 2
24
24
 
25
25
  each_embedded :'<%= plural_name %>' do
@@ -31,12 +31,12 @@ describe <%= model_class_name %>, type: :integration do
31
31
  end
32
32
  <% if params.size > 0 %>
33
33
  it "can create <%= plural_name %>" do
34
- get <%= plural_name %>_uri
34
+ get <%= collection_name %>_uri
35
35
 
36
36
  _(link_rels).must_include(:'create-form')
37
37
  follow_rel :'create-form'
38
- _(links[:self][:href]).must_equal new_<%= name %>_uri
39
- _(attributes[:href]).must_equal <%= plural_name %>_uri
38
+ _(links[:self][:href]).must_equal new_<%= resource_name %>_uri
39
+ _(attributes[:href]).must_equal <%= collection_name %>_uri
40
40
  _(attributes[:method]).must_equal "POST"
41
41
  _(attributes[:name]).must_equal "create-<%= name %>"
42
42
  _(attributes[:title]).must_equal "Create <%= model_class_name %>"
@@ -49,9 +49,9 @@ describe <%= model_class_name %>, type: :integration do
49
49
  _(link_rels).must_include(:self)
50
50
  _(headers["Location"]).must_equal links[:self][:href]
51
51
 
52
- get <%= plural_name %>_uri
52
+ get <%= collection_name %>_uri
53
53
  _(status).must_equal 200
54
- _(links[:self][:href]).must_include <%= plural_name %>_uri
54
+ _(links[:self][:href]).must_include <%= collection_name %>_uri
55
55
  _(embedded(:'<%= plural_name %>').size).must_equal 1
56
56
 
57
57
  embedded :'<%= plural_name %>' do
@@ -68,14 +68,14 @@ describe <%= model_class_name %>, type: :integration do
68
68
 
69
69
  it "<%= plural_name %> can be updated" do
70
70
  <%= name %> = <%= model_class_name %>.create
71
- get <%= name %>_uri(<%= name %>)
71
+ get <%= resource_name %>_uri(<%= name %>)
72
72
  _(status).must_equal 200
73
73
 
74
74
  _(link_rels).must_include(:'edit-form')
75
75
  follow_rel :'edit-form'
76
76
 
77
- _(links[:self][:href]).must_equal edit_<%= name %>_uri(<%= name %>)
78
- _(attributes[:href]).must_equal <%= name %>_uri(<%= name %>)
77
+ _(links[:self][:href]).must_equal edit_<%= resource_name %>_uri(<%= name %>)
78
+ _(attributes[:href]).must_equal <%= resource_name %>_uri(<%= name %>)
79
79
  _(attributes[:method]).must_equal "PUT"
80
80
  _(attributes[:name]).must_equal "update-<%= name %>"
81
81
  _(attributes[:title]).must_equal "Update <%= model_class_name %>"
@@ -91,14 +91,14 @@ describe <%= model_class_name %>, type: :integration do
91
91
 
92
92
  it "<%= plural_name %> can be deleted" do
93
93
  <%= name %> = <%= model_class_name %>.create
94
- get <%= name %>_uri(<%= name %>)
94
+ get <%= resource_name %>_uri(<%= name %>)
95
95
  _(status).must_equal 200
96
96
  _(link_rels).must_include(:'doc:delete')
97
97
 
98
98
  follow_rel(:'doc:delete', method: :delete)
99
99
  _(status).must_equal 204
100
100
 
101
- get <%= name %>_uri(<%= name %>)
101
+ get <%= resource_name %>_uri(<%= name %>)
102
102
  _(status).must_equal 404
103
103
  end
104
104
 
@@ -12,7 +12,7 @@ module Shaf
12
12
  unless collection.respond_to? :paginate
13
13
  log.warn "Trying to paginate a collection that doesn't " \
14
14
  "support pagination: #{collection}"
15
- return
15
+ return collection
16
16
  end
17
17
 
18
18
  per_page = params[:per_page].to_i if params[:per_page]
data/lib/shaf/profile.rb CHANGED
@@ -5,6 +5,14 @@ require 'shaf/extensions/resource_uris'
5
5
 
6
6
  module Shaf
7
7
  class Profile
8
+ module NormalizeName
9
+ private def normalize(name)
10
+ name.to_s.downcase.tr('-', '_')
11
+ end
12
+ end
13
+
14
+ extend NormalizeName
15
+ include NormalizeName
8
16
  include Shaf::UriHelper
9
17
 
10
18
  class << self
@@ -98,18 +106,10 @@ module Shaf
98
106
  def evaluator
99
107
  Evaluator.new(parent: self)
100
108
  end
101
-
102
- def normalize(name)
103
- name.to_s.downcase.tr('-', '_')
104
- end
105
109
  end
106
110
 
107
111
  def name
108
112
  normalize(self.class.name)
109
113
  end
110
-
111
- def normalize(str)
112
- self.class.normalize(str)
113
- end
114
114
  end
115
115
  end
@@ -1,15 +1,58 @@
1
1
  module Shaf
2
2
  module RegistrableFactory
3
-
4
3
  class NotFoundError < StandardError; end
4
+ class NoIdentifiersError < StandardError
5
+
6
+ def initialize(clazz)
7
+ super <<~ERR
8
+ #{clazz} does not have an @identifiers ivar.
9
+ Did you perhaps forget to call `#{clazz}.identifier`?
10
+ ERR
11
+ end
12
+ end
13
+
14
+ class Entry
15
+ attr_reader :clazz
16
+
17
+ def initialize(clazz)
18
+ @clazz = clazz
19
+ end
20
+
21
+ def match?(strings)
22
+ raise NoIdentifiersError, clazz unless identifiers
23
+ return false if strings.size < identifiers.size
24
+ identifiers.zip(strings).all? { |pattern, str| matching_identifier? str, pattern }
25
+ end
26
+
27
+ def identifier_count
28
+ identifiers&.size || 0
29
+ end
30
+
31
+ def usage
32
+ clazz.instance_variable_get(:@usage)
33
+ end
34
+
35
+ private
36
+
37
+ def identifiers
38
+ clazz.identified_by
39
+ end
40
+
41
+ def matching_identifier?(str, pattern)
42
+ return false if pattern.nil? || str.nil? || str.empty?
43
+ pattern = pattern.to_s if pattern.is_a? Symbol
44
+ return str == pattern if pattern.is_a? String
45
+ !!str.match(pattern)
46
+ end
47
+ end
5
48
 
6
49
  def all
7
- reg.dup
50
+ reg.map(&:clazz)
8
51
  end
9
52
 
10
- def each
53
+ def each(&block)
11
54
  return all.each unless block_given?
12
- all.each { |c| yield c }
55
+ all.each(&block)
13
56
  end
14
57
 
15
58
  def size
@@ -17,34 +60,31 @@ module Shaf
17
60
  end
18
61
 
19
62
  def register(clazz)
20
- reg << clazz
63
+ reg << Entry.new(clazz)
21
64
  end
22
65
 
23
66
  def unregister(*str)
24
67
  return if str.empty? || !str.all?
25
- reg.delete_if { |clazz| matching_class? str, clazz }
68
+ reg.delete_if { |entry| entry.match? str }
26
69
  end
27
70
 
28
71
  def lookup(*str)
29
- return if str.empty? || !str.all?
30
- reg.select { |clazz| matching_class? str, clazz }
31
- .sort_by(&method(:identifier_count))
32
- .last
72
+ lookup_entry(*str)&.clazz
33
73
  end
34
74
 
35
75
  def usage
36
76
  reg.compact.map do |entry|
37
- usage = entry.instance_variable_get(:@usage)
77
+ usage = entry.usage
38
78
  usage.respond_to?(:call) ? usage.call : usage
39
79
  end
40
80
  end
41
81
 
42
82
  def create(*params, **options)
43
- clazz = lookup(*params)
44
- raise NotFoundError.new(%Q(Command '#{ARGV}' is not supported)) unless clazz
83
+ entry = lookup_entry(*params)
84
+ raise NotFoundError.new(%Q(Command '#{ARGV}' is not supported)) unless entry
45
85
 
46
- args = init_args(clazz, params)
47
- clazz.new(*args, **options)
86
+ args = init_args(entry, params)
87
+ entry.clazz.new(*args, **options)
48
88
  end
49
89
 
50
90
  private
@@ -53,25 +93,15 @@ module Shaf
53
93
  @reg ||= []
54
94
  end
55
95
 
56
- def matching_class?(strings, clazz)
57
- identifiers = clazz.instance_variable_get(:@identifiers)
58
- return false if strings.size < identifiers.size
59
- identifiers.zip(strings).all? { |pattern, str| matching_identifier? str, pattern }
60
- end
61
-
62
- def matching_identifier?(str, pattern)
63
- return false if pattern.nil? || str.nil? || str.empty?
64
- pattern = pattern.to_s if pattern.is_a? Symbol
65
- return str == pattern if pattern.is_a? String
66
- str.match(pattern) || false
67
- end
68
-
69
- def identifier_count(clazz)
70
- clazz.instance_variable_get(:@identifiers)&.size || 0
96
+ def lookup_entry(*str)
97
+ return if str.empty? || !str.all?
98
+ reg.select { |entry| entry.match? str }
99
+ .sort_by { |entry| entry.identifier_count }
100
+ .last
71
101
  end
72
102
 
73
- def init_args(clazz, params)
74
- first_non_id = identifier_count(clazz)
103
+ def init_args(entry, params)
104
+ first_non_id = entry.identifier_count
75
105
  params[first_non_id..-1]
76
106
  end
77
107
  end
@@ -16,7 +16,7 @@ module Shaf
16
16
 
17
17
  def hash
18
18
  {
19
- status: controller.status,
19
+ status: status,
20
20
  type: code,
21
21
  title: title,
22
22
  detail: resource.message,
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
- @controllers ||= []
28
- @controllers << controller
70
+ controllers << controller
29
71
  end
30
72
 
31
73
  def routes
32
- init_routes unless defined? @routes
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
- attr_reader :controllers
85
+ def controllers
86
+ @controllers ||= []
87
+ end
45
88
 
46
89
  def init_routes
47
- @routes = Hash.new do |hash, key|
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| init_routes_for(controller) }
93
+ controllers.each { |controller| init(controller, routes) }
94
+ routes
51
95
  end
52
96
 
53
- def init_routes_for(controller)
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
- controllers_for(http_method, path) do |controller|
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 controllers_for(http_method, path)
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)