moonrope 1.3.3 → 2.0.2

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 (78) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +9 -0
  3. data/Gemfile.lock +47 -0
  4. data/MIT-LICENCE +20 -0
  5. data/README.md +24 -0
  6. data/bin/moonrope +28 -0
  7. data/docs/authentication.md +114 -0
  8. data/docs/controllers.md +106 -0
  9. data/docs/exceptions.md +27 -0
  10. data/docs/introduction.md +29 -0
  11. data/docs/structures.md +214 -0
  12. data/example/authentication.rb +50 -0
  13. data/example/controllers/meta_controller.rb +14 -0
  14. data/example/controllers/users_controller.rb +92 -0
  15. data/example/structures/pet_structure.rb +12 -0
  16. data/example/structures/user_structure.rb +35 -0
  17. data/lib/moonrope.rb +5 -4
  18. data/lib/moonrope/action.rb +170 -40
  19. data/lib/moonrope/authenticator.rb +42 -0
  20. data/lib/moonrope/base.rb +67 -6
  21. data/lib/moonrope/controller.rb +4 -2
  22. data/lib/moonrope/doc_context.rb +94 -0
  23. data/lib/moonrope/doc_server.rb +123 -0
  24. data/lib/moonrope/dsl/action_dsl.rb +159 -9
  25. data/lib/moonrope/dsl/authenticator_dsl.rb +35 -0
  26. data/lib/moonrope/dsl/base_dsl.rb +21 -18
  27. data/lib/moonrope/dsl/controller_dsl.rb +60 -9
  28. data/lib/moonrope/dsl/filterable_dsl.rb +27 -0
  29. data/lib/moonrope/dsl/structure_dsl.rb +28 -2
  30. data/lib/moonrope/errors.rb +13 -0
  31. data/lib/moonrope/eval_environment.rb +82 -3
  32. data/lib/moonrope/eval_helpers.rb +47 -8
  33. data/lib/moonrope/eval_helpers/filter_helper.rb +82 -0
  34. data/lib/moonrope/guard.rb +35 -0
  35. data/lib/moonrope/html_generator.rb +65 -0
  36. data/lib/moonrope/param_set.rb +11 -1
  37. data/lib/moonrope/rack_middleware.rb +66 -37
  38. data/lib/moonrope/railtie.rb +31 -14
  39. data/lib/moonrope/request.rb +43 -15
  40. data/lib/moonrope/structure.rb +100 -18
  41. data/lib/moonrope/structure_attribute.rb +39 -0
  42. data/lib/moonrope/version.rb +1 -1
  43. data/moonrope.gemspec +21 -0
  44. data/spec/spec_helper.rb +32 -0
  45. data/spec/specs/action_spec.rb +455 -0
  46. data/spec/specs/base_spec.rb +29 -0
  47. data/spec/specs/controller_spec.rb +31 -0
  48. data/spec/specs/param_set_spec.rb +31 -0
  49. data/templates/basic/_action_form.erb +77 -0
  50. data/templates/basic/_errors_table.erb +32 -0
  51. data/templates/basic/_structure_attributes_list.erb +55 -0
  52. data/templates/basic/action.erb +168 -0
  53. data/templates/basic/assets/lock.svg +3 -0
  54. data/templates/basic/assets/reset.css +101 -0
  55. data/templates/basic/assets/style.css +348 -0
  56. data/templates/basic/assets/tool.svg +4 -0
  57. data/templates/basic/assets/try.js +157 -0
  58. data/templates/basic/authenticator.erb +52 -0
  59. data/templates/basic/controller.erb +20 -0
  60. data/templates/basic/index.erb +114 -0
  61. data/templates/basic/layout.erb +46 -0
  62. data/templates/basic/structure.erb +23 -0
  63. data/test/test_helper.rb +81 -0
  64. data/test/tests/action_access_test.rb +63 -0
  65. data/test/tests/actions_test.rb +524 -0
  66. data/test/tests/authenticators_test.rb +87 -0
  67. data/test/tests/base_test.rb +35 -0
  68. data/test/tests/controllers_test.rb +49 -0
  69. data/test/tests/eval_environment_test.rb +136 -0
  70. data/test/tests/evel_helpers_test.rb +60 -0
  71. data/test/tests/examples_test.rb +11 -0
  72. data/test/tests/helpers_test.rb +97 -0
  73. data/test/tests/param_set_test.rb +44 -0
  74. data/test/tests/rack_middleware_test.rb +131 -0
  75. data/test/tests/request_test.rb +232 -0
  76. data/test/tests/structures_param_extensions_test.rb +159 -0
  77. data/test/tests/structures_test.rb +398 -0
  78. metadata +71 -56
@@ -0,0 +1,35 @@
1
+ module Moonrope
2
+ module DSL
3
+ class AuthenticatorDSL
4
+
5
+ def initialize(authenticator)
6
+ @authenticator = authenticator
7
+ end
8
+
9
+ def friendly_name(value)
10
+ @authenticator.friendly_name = value
11
+ end
12
+
13
+ def description(value)
14
+ @authenticator.description = value
15
+ end
16
+
17
+ def header(name, description = nil, options = {})
18
+ @authenticator.headers[name] = options.merge(:name => name, :description => description)
19
+ end
20
+
21
+ def error(name, description = nil, options = {})
22
+ @authenticator.errors[name] = options.merge(:name => name, :description => description)
23
+ end
24
+
25
+ def lookup(&block)
26
+ @authenticator.lookup = block
27
+ end
28
+
29
+ def rule(name, error_code, description = nil, &block)
30
+ @authenticator.rules[name] = {:name => name, :error_code => error_code, :description => description, :block => block}
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,7 @@
1
+ require 'moonrope/structure'
2
+ require 'moonrope/controller'
3
+ require 'moonrope/authenticator'
4
+
1
5
  module Moonrope
2
6
  module DSL
3
7
  class BaseDSL
@@ -47,24 +51,6 @@ module Moonrope
47
51
  controller
48
52
  end
49
53
 
50
- #
51
- # Set the authenticator for the API.
52
- #
53
- # @yield stores the block as the authenticator
54
- #
55
- def authenticator(&block)
56
- @base.authenticator = block
57
- end
58
-
59
- #
60
- # Set the default access check block.
61
- #
62
- # @yield stores the block as the access check
63
- #
64
- def default_access(value = nil, &block)
65
- @base.default_access = block_given? ? block : value
66
- end
67
-
68
54
  #
69
55
  # Define a new helper in the global namespace
70
56
  #
@@ -81,6 +67,23 @@ module Moonrope
81
67
  helper_instance
82
68
  end
83
69
 
70
+ #
71
+ # Define a new authenticator
72
+ #
73
+ def authenticator(name, &block)
74
+ authenticator = Moonrope::Authenticator.new(name)
75
+ dsl = Moonrope::DSL::AuthenticatorDSL.new(authenticator)
76
+ dsl.instance_eval(&block) if block_given?
77
+ @base.authenticators[name] = authenticator
78
+ end
79
+
80
+ #
81
+ # Define a new global shared action
82
+ #
83
+ def shared_action(name, &block)
84
+ @base.shared_actions[name] = block
85
+ end
86
+
84
87
  end
85
88
  end
86
89
  end
@@ -1,3 +1,6 @@
1
+ require 'moonrope/action'
2
+ require 'moonrope/before_action'
3
+
1
4
  module Moonrope
2
5
  module DSL
3
6
  class ControllerDSL
@@ -14,6 +17,31 @@ module Moonrope
14
17
  # @return [Moonrope::Controller] the associated controller
15
18
  attr_reader :controller
16
19
 
20
+ #
21
+ # Stop this controller frmo being documented
22
+ #
23
+ def no_doc!
24
+ @controller.doc = false
25
+ end
26
+
27
+ #
28
+ # Set the friendly name for the controller
29
+ #
30
+ # @param name [String]
31
+ #
32
+ def friendly_name(string)
33
+ @controller.friendly_name = string
34
+ end
35
+
36
+ #
37
+ # Set the description for the controller
38
+ #
39
+ # @param description [String]
40
+ #
41
+ def description(description)
42
+ @controller.description = description
43
+ end
44
+
17
45
  #
18
46
  # Defines a new action within the controller.
19
47
  #
@@ -28,6 +56,29 @@ module Moonrope
28
56
  action
29
57
  end
30
58
 
59
+ #
60
+ # Set the name of the authenticator to use for all actions in this controller
61
+ #
62
+ # @param name [Symbol]
63
+ #
64
+ def authenticator(name)
65
+ @controller.authenticator = name
66
+ end
67
+
68
+ #
69
+ # Set the name of the access rule to use for all actions in this controller
70
+ #
71
+ # @param name [Symbol]
72
+ #
73
+ def access_rule(name)
74
+ if name.is_a?(Hash)
75
+ authenticator name.first[0]
76
+ access_rule name.first[1]
77
+ else
78
+ @controller.access_rule = name
79
+ end
80
+ end
81
+
31
82
  #
32
83
  # Defines a new before action within the controller.
33
84
  #
@@ -43,14 +94,6 @@ module Moonrope
43
94
  before_action
44
95
  end
45
96
 
46
- #
47
- # Defines the access required for controller methods which do not
48
- # define their own access.
49
- #
50
- def access(value = nil, &block)
51
- @controller.access = block_given? ? block : value
52
- end
53
-
54
97
  #
55
98
  # Defines a new helper for this controller.
56
99
  #
@@ -61,9 +104,17 @@ module Moonrope
61
104
  if @controller.base.helper(name, @controller)
62
105
  raise Moonrope::Errors::HelperAlreadyDefined, "Helper has already been defined with name `#{name}`"
63
106
  end
64
-
65
107
  @controller.base.helpers << Moonrope::Helper.new(name, @controller, options, &block)
66
108
  end
109
+
110
+ #
111
+ # Define a shared action which can be used by any action
112
+ #
113
+ # @param name[Symbol] the name of the shared action
114
+ #
115
+ def shared_action(name, &block)
116
+ @controller.shared_actions[name] = block
117
+ end
67
118
  end
68
119
  end
69
120
  end
@@ -0,0 +1,27 @@
1
+ module Moonrope
2
+ module DSL
3
+ class FilterableDSL
4
+
5
+ def initialize(action)
6
+ @action = action
7
+ end
8
+
9
+ def attribute(name, options = {}, &block)
10
+ if options[:type] == Integer || options[:type] == Float
11
+ # Numbers
12
+ options[:operators] ||= [:eq, :not_eq, :gt, :gte, :lt, :lte, :in, :not_in]
13
+ elsif options[:type] == String
14
+ # Strings
15
+ options[:operators] ||= [:eq, :not_eq, :starts_with, :ends_with, :in, :not_in]
16
+ elsif options[:type] == :timestamp
17
+ # Times
18
+ options[:operators] ||= [:eq, :not_eq, :gt, :gte, :lt, :lte]
19
+ else
20
+ # Everything else
21
+ options[:operators] ||= [:eq, :not_eq]
22
+ end
23
+ @action.filters[name] = options.merge(:name => name, :block => block)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,3 +1,5 @@
1
+ require 'moonrope/structure_attribute'
2
+
1
3
  module Moonrope
2
4
  module DSL
3
5
  class StructureDSL
@@ -26,6 +28,10 @@ module Moonrope
26
28
  @conditions = []
27
29
  end
28
30
 
31
+ def no_doc!
32
+ @structure.doc = false
33
+ end
34
+
29
35
  def scope(options = {}, &block)
30
36
  scope_dsl = self.class.new(@structure)
31
37
  scope_dsl.options = options
@@ -35,12 +41,21 @@ module Moonrope
35
41
  def group(name, &block)
36
42
  scope_dsl = self.class.new(@structure)
37
43
  scope_dsl.groups = [@groups, name].flatten
44
+ scope_dsl.conditions = @conditions
38
45
  scope_dsl.instance_eval(&block)
39
46
  end
40
47
 
41
- def condition(condition, &block)
48
+ def condition(condition, description = nil, &block)
42
49
  scope_dsl = self.class.new(@structure)
50
+ if condition.is_a?(Hash) && condition.size == 1
51
+ condition = {:authenticator => condition.first[0], :access_rule => condition.first[1]}
52
+ elsif condition.is_a?(Symbol)
53
+ condition = {:authenticator => :default, :access_rule => condition}
54
+ else
55
+ condition = {:block => condition, :description => description}
56
+ end
43
57
  scope_dsl.conditions = [@conditions, condition].flatten
58
+ scope_dsl.groups = @groups
44
59
  scope_dsl.instance_eval(&block)
45
60
  end
46
61
 
@@ -61,6 +76,8 @@ module Moonrope
61
76
  attribute.source_attribute = options[:source_attribute]
62
77
  attribute.value = options[:value]
63
78
  attribute.example = options[:eg] || options[:example]
79
+ attribute.doc = options[:doc]
80
+ attribute.mutation = options[:mutation]
64
81
  attribute.groups = @groups
65
82
  attribute.conditions = @conditions
66
83
  @structure.attributes[type] << attribute
@@ -84,7 +101,16 @@ module Moonrope
84
101
 
85
102
  def expansion(name, *args, &block)
86
103
  if block_given?
87
- @structure.expansions[name] = block
104
+ if args[0].is_a?(String)
105
+ attrs = args[1] || {}
106
+ attrs[:description] = args[0]
107
+ elsif args[0].is_a?(Hash)
108
+ attrs = atrs[0]
109
+ else
110
+ attrs = {}
111
+ end
112
+
113
+ @structure.expansions[name] = attrs.merge({:block => block, :conditions => @conditions})
88
114
  else
89
115
  attribute(:expansion, name, *args)
90
116
  end
@@ -5,6 +5,9 @@ module Moonrope
5
5
  end
6
6
 
7
7
  class HelperAlreadyDefined < Error; end
8
+ class MissingAuthenticator < Error; end
9
+ class MissingAccessRule < Error; end
10
+ class InvalidSharedAction < Error; end
8
11
 
9
12
  class RequestError < Error
10
13
  attr_reader :options
@@ -22,6 +25,16 @@ module Moonrope
22
25
  end
23
26
  end
24
27
 
28
+ class StructuredError < RequestError
29
+ def status
30
+ "error"
31
+ end
32
+
33
+ def data
34
+ @options
35
+ end
36
+ end
37
+
25
38
  class AccessDenied < RequestError
26
39
  def status
27
40
  'access-denied'
@@ -1,7 +1,11 @@
1
+ require 'moonrope/eval_helpers'
2
+ require 'moonrope/eval_helpers/filter_helper'
3
+
1
4
  module Moonrope
2
5
  class EvalEnvironment
3
6
 
4
7
  include Moonrope::EvalHelpers
8
+ include Moonrope::EvalHelpers::FilterHelper
5
9
 
6
10
  # @return [Moonrope::Base] the base object
7
11
  attr_reader :base
@@ -28,9 +32,10 @@ module Moonrope
28
32
  # @param request [Moonrope::Request]
29
33
  # @param accessors [Hash] additional variables which can be made available
30
34
  #
31
- def initialize(base, request, accessors = {})
35
+ def initialize(base, request, action = nil, accessors = {})
32
36
  @base = base
33
37
  @request = request
38
+ @action = action
34
39
  @accessors = accessors
35
40
  @default_params = {}
36
41
  reset
@@ -46,8 +51,8 @@ module Moonrope
46
51
  #
47
52
  # @return [Object] the authenticated object
48
53
  #
49
- def auth
50
- request ? request.authenticated_user : nil
54
+ def identity
55
+ request ? request.identity : nil
51
56
  end
52
57
 
53
58
  #
@@ -143,6 +148,54 @@ module Moonrope
143
148
  raise Moonrope::Errors::Error, "No structure found named '#{structure_name}'"
144
149
  end
145
150
 
151
+ if options.delete(:return)
152
+ if options.empty? && action && action.returns && action.returns[:structure_opts].is_a?(Hash)
153
+ options = action.returns[:structure_opts]
154
+ end
155
+ end
156
+
157
+ if request
158
+ if options[:paramable]
159
+ if options[:paramable].is_a?(Hash)
160
+ options[:expansions] = options[:paramable][:expansions]
161
+ options[:full] = options[:paramable][:full]
162
+ end
163
+
164
+ if options[:paramable] == true || options[:paramable].is_a?(Hash) && options[:paramable].has_key?(:expansions)
165
+ if request.params["_expansions"].is_a?(Array)
166
+ options[:expansions] = request.params["_expansions"].map(&:to_sym)
167
+ if options[:paramable].is_a?(Hash) && options[:paramable][:expansions].is_a?(Array)
168
+ whitelist = options[:paramable][:expansions]
169
+ options[:expansions].reject! { |e| !whitelist.include?(e) }
170
+ end
171
+ end
172
+
173
+ if request.params["_expansions"] == true
174
+ if options[:paramable].is_a?(Hash)
175
+ if options[:paramable][:expansions].is_a?(Array)
176
+ options[:expansions] = options[:paramable][:expansions]
177
+ elsif options[:paramable].has_key?(:expansions)
178
+ options[:expansions] = true
179
+ end
180
+ else
181
+ options[:expansions] = true
182
+ end
183
+ end
184
+
185
+ if request.params["_expansions"] == false
186
+ options[:expansions] = nil
187
+ end
188
+
189
+ end
190
+
191
+ if request.params.has?("_full")
192
+ if options[:paramable] == true || (options[:paramable].is_a?(Hash) && options[:paramable].has_key?(:full))
193
+ options[:full] = !!request.params["_full"]
194
+ end
195
+ end
196
+ end
197
+ end
198
+
146
199
  structure.hash(object, options.merge(:request => @request))
147
200
  end
148
201
 
@@ -169,5 +222,31 @@ module Moonrope
169
222
  self.structure_for(structure_name).is_a?(Moonrope::Structure)
170
223
  end
171
224
 
225
+ #
226
+ # Copy the list of parameters onto the given objectr
227
+ #
228
+ def copy_params_to(object, *params_to_copy)
229
+ if params_to_copy.first.is_a?(Hash)
230
+ options = params_to_copy.shift
231
+ if options[:from]
232
+ all_params = action.params.select { |_,p| p[:from_shared_action].include?(options[:from]) }
233
+ params_to_copy = params_to_copy + all_params.keys
234
+ end
235
+ end
236
+ params_to_copy.each do |param_name|
237
+ if param_definition = action.params[param_name]
238
+ if params.has?(param_name)
239
+ if param_definition[:apply]
240
+ instance_exec(object, params[param_name], &param_definition[:apply])
241
+ elsif object.respond_to?("#{param_name}=")
242
+ object.send("#{param_name}=", params[param_name])
243
+ end
244
+ end
245
+ else
246
+ raise Moonrope::Errors::Error, "Attempted to copy parameter #{parameter} to object but no definition exists"
247
+ end
248
+ end
249
+ end
250
+
172
251
  end
173
252
  end
@@ -7,29 +7,68 @@ module Moonrope
7
7
  # @param type [Symbol] the type of error to raise
8
8
  # @param message [String, Hash or Array] options to pass with the error (usually a message)
9
9
  #
10
- def error(type, message)
10
+ def error(type, code_or_message = nil, message = nil)
11
11
  case type
12
- when :not_found then raise(Moonrope::Errors::NotFound, message)
13
- when :access_denied then raise(Moonrope::Errors::AccessDenied, message)
14
- when :validation_error then raise(Moonrope::Errors::ValidationError, message)
15
- when :parameter_error then raise(Moonrope::Errors::ParameterError, message)
12
+ when :not_found then raise(Moonrope::Errors::NotFound, code_or_message)
13
+ when :access_denied then raise(Moonrope::Errors::AccessDenied, code_or_message)
14
+ when :validation_error then raise(Moonrope::Errors::ValidationError, code_or_message)
15
+ when :parameter_error then raise(Moonrope::Errors::ParameterError, code_or_message)
16
+ when :structured_error then structured_error(code_or_message, message)
16
17
  else
17
- raise Moonrope::Errors::RequestError, message
18
+ if type.is_a?(String)
19
+ if code_or_message.is_a?(Hash)
20
+ structured_error(type, nil, code_or_message)
21
+ else
22
+ structured_error(type, code_or_message, message.is_a?(Hash) ? message : {})
23
+ end
24
+ else
25
+ raise Moonrope::Errors::RequestError, code_or_message
26
+ end
18
27
  end
19
28
  end
20
29
 
30
+ #
31
+ # Raises a structured error.
32
+ #
33
+ # @param code [String] the code to return
34
+ # @param message [String] explantory text to return
35
+ # @param additional [Hash] additional data to return with the error
36
+ #
37
+ def structured_error(code, message, additional = {})
38
+ if action
39
+ if action.authenticator_to_use.is_a?(Moonrope::Authenticator)
40
+ errors = action.authenticator_to_use.errors.merge(action.errors)
41
+ else
42
+ errors = action.errors
43
+ end
44
+ if error = errors[code]
45
+ message = error[:description].gsub(/\{(\w+)\}/) { additional[$1.to_sym] }
46
+ end
47
+ end
48
+ raise Moonrope::Errors::StructuredError, {:code => code, :message => message}.merge(additional)
49
+ end
50
+
21
51
  #
22
52
  # Return paginated information
23
53
  #
24
54
  def paginate(collection, max_per_page = 60, &block)
25
55
  per_page = params.per_page || 30
26
56
  per_page = max_per_page if per_page < 1 || per_page > max_per_page
27
- paginated_results = collection.page(params.page).per(per_page)
28
- set_flag :paginated, {:page => params.page, :per_page => per_page, :total_pages => paginated_results.total_pages, :total_records => paginated_results.total_count}
57
+ paginated_results = collection.page(params.page || 1).per(per_page)
58
+ set_flag :paginated, {:page => params.page || 1, :per_page => per_page, :total_pages => paginated_results.total_pages, :total_records => paginated_results.total_count}
29
59
  paginated_results.to_a.map do |result|
30
60
  block.call(result)
31
61
  end
32
62
  end
33
63
 
64
+ #
65
+ # Return information sorted by appropriate values from parameters
66
+ #
67
+ def sort(collection, &block)
68
+ collection = collection.order(params.sort_by => params.order)
69
+ set_flag :sorted, {:by => params.sort_by, :order => params.order}
70
+ block.call(collection)
71
+ end
72
+
34
73
  end
35
74
  end