moonrope 1.4.1 → 2.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.
- checksums.yaml +4 -4
- data/Gemfile +9 -0
- data/Gemfile.lock +47 -0
- data/MIT-LICENCE +20 -0
- data/README.md +24 -0
- data/bin/moonrope +28 -0
- data/docs/authentication.md +114 -0
- data/docs/controllers.md +106 -0
- data/docs/exceptions.md +27 -0
- data/docs/introduction.md +29 -0
- data/docs/structures.md +214 -0
- data/example/authentication.rb +50 -0
- data/example/controllers/meta_controller.rb +14 -0
- data/example/controllers/users_controller.rb +92 -0
- data/example/structures/pet_structure.rb +12 -0
- data/example/structures/user_structure.rb +35 -0
- data/html/assets/lock.svg +3 -0
- data/html/assets/reset.css +101 -0
- data/html/assets/style.css +348 -0
- data/html/assets/tool.svg +4 -0
- data/html/assets/try.js +151 -0
- data/html/authenticators/default.html +191 -0
- data/html/controllers/meta/version.html +144 -0
- data/html/controllers/meta.html +73 -0
- data/html/controllers/users/create.html +341 -0
- data/html/controllers/users/list.html +348 -0
- data/html/controllers/users/show.html +261 -0
- data/html/controllers/users/update.html +387 -0
- data/html/controllers/users.html +93 -0
- data/html/index.html +166 -0
- data/html/moonrope.txt +0 -0
- data/html/structures/pet.html +176 -0
- data/html/structures/user.html +338 -0
- data/lib/moonrope/action.rb +165 -37
- data/lib/moonrope/authenticator.rb +39 -0
- data/lib/moonrope/base.rb +24 -6
- data/lib/moonrope/controller.rb +4 -2
- data/lib/moonrope/doc_context.rb +94 -0
- data/lib/moonrope/doc_server.rb +123 -0
- data/lib/moonrope/dsl/action_dsl.rb +159 -9
- data/lib/moonrope/dsl/authenticator_dsl.rb +31 -0
- data/lib/moonrope/dsl/base_dsl.rb +21 -18
- data/lib/moonrope/dsl/controller_dsl.rb +60 -9
- data/lib/moonrope/dsl/filterable_dsl.rb +27 -0
- data/lib/moonrope/dsl/structure_dsl.rb +27 -2
- data/lib/moonrope/errors.rb +3 -0
- data/lib/moonrope/eval_environment.rb +82 -3
- data/lib/moonrope/eval_helpers/filter_helper.rb +82 -0
- data/lib/moonrope/eval_helpers.rb +28 -5
- data/lib/moonrope/guard.rb +35 -0
- data/lib/moonrope/html_generator.rb +65 -0
- data/lib/moonrope/param_set.rb +11 -1
- data/lib/moonrope/rack_middleware.rb +1 -1
- data/lib/moonrope/railtie.rb +31 -14
- data/lib/moonrope/request.rb +25 -14
- data/lib/moonrope/structure.rb +74 -11
- data/lib/moonrope/structure_attribute.rb +15 -0
- data/lib/moonrope/version.rb +1 -1
- data/lib/moonrope.rb +5 -4
- data/moonrope.gemspec +21 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/specs/action_spec.rb +455 -0
- data/spec/specs/base_spec.rb +29 -0
- data/spec/specs/controller_spec.rb +31 -0
- data/spec/specs/param_set_spec.rb +31 -0
- data/templates/basic/_action_form.erb +77 -0
- data/templates/basic/_errors_table.erb +32 -0
- data/templates/basic/_structure_attributes_list.erb +55 -0
- data/templates/basic/action.erb +168 -0
- data/templates/basic/assets/lock.svg +3 -0
- data/templates/basic/assets/reset.css +101 -0
- data/templates/basic/assets/style.css +348 -0
- data/templates/basic/assets/tool.svg +4 -0
- data/templates/basic/assets/try.js +151 -0
- data/templates/basic/authenticator.erb +51 -0
- data/templates/basic/controller.erb +20 -0
- data/templates/basic/index.erb +114 -0
- data/templates/basic/layout.erb +46 -0
- data/templates/basic/structure.erb +23 -0
- data/test/test_helper.rb +81 -0
- data/test/tests/action_access_test.rb +63 -0
- data/test/tests/actions_test.rb +524 -0
- data/test/tests/authenticators_test.rb +87 -0
- data/test/tests/base_test.rb +35 -0
- data/test/tests/controllers_test.rb +49 -0
- data/test/tests/eval_environment_test.rb +136 -0
- data/test/tests/evel_helpers_test.rb +60 -0
- data/test/tests/examples_test.rb +11 -0
- data/test/tests/helpers_test.rb +97 -0
- data/test/tests/param_set_test.rb +44 -0
- data/test/tests/rack_middleware_test.rb +109 -0
- data/test/tests/request_test.rb +232 -0
- data/test/tests/structures_param_extensions_test.rb +159 -0
- data/test/tests/structures_test.rb +335 -0
- metadata +82 -48
@@ -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,7 @@ 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]
|
64
80
|
attribute.groups = @groups
|
65
81
|
attribute.conditions = @conditions
|
66
82
|
@structure.attributes[type] << attribute
|
@@ -84,7 +100,16 @@ module Moonrope
|
|
84
100
|
|
85
101
|
def expansion(name, *args, &block)
|
86
102
|
if block_given?
|
87
|
-
|
103
|
+
if args[0].is_a?(String)
|
104
|
+
attrs = args[1] || {}
|
105
|
+
attrs[:description] = args[0]
|
106
|
+
elsif args[0].is_a?(Hash)
|
107
|
+
attrs = atrs[0]
|
108
|
+
else
|
109
|
+
attrs = {}
|
110
|
+
end
|
111
|
+
|
112
|
+
@structure.expansions[name] = attrs.merge({:block => block, :conditions => @conditions})
|
88
113
|
else
|
89
114
|
attribute(:expansion, name, *args)
|
90
115
|
end
|
data/lib/moonrope/errors.rb
CHANGED
@@ -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
|
50
|
-
request ? request.
|
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], ¶m_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
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Moonrope
|
2
|
+
module EvalHelpers
|
3
|
+
module FilterHelper
|
4
|
+
|
5
|
+
#
|
6
|
+
# Return information which has been passed through the filterable filters
|
7
|
+
#
|
8
|
+
def filter(collection, &block)
|
9
|
+
filters_flags = {}
|
10
|
+
if params.filters.is_a?(Hash)
|
11
|
+
params.filters.each do |key, value|
|
12
|
+
options = {}
|
13
|
+
if filter = action.filters[key.to_sym]
|
14
|
+
if value.is_a?(Hash)
|
15
|
+
options[:operator] = value['operator'] ? value['operator'].to_sym : filter[:operators].first
|
16
|
+
options[:value] = value['value']
|
17
|
+
else
|
18
|
+
# If no hash is provided, we'll always attempt to use the first operator
|
19
|
+
# for the filter.
|
20
|
+
options[:operator] = filter[:operators].first
|
21
|
+
options[:value] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
# Check that the operator is supported
|
25
|
+
unless filter[:operators].include?(options[:operator])
|
26
|
+
error 'FilterError', :issue_code => 'InvalidOperator', :issue_message => "The operator '#{options[:operator]}' is not supported for the '#{key}' attribute."
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add this item to the flags which will be set at the end of the method
|
30
|
+
filters_flags[key] = options
|
31
|
+
|
32
|
+
# Do the filtering...
|
33
|
+
if filter[:block]
|
34
|
+
# If a block is provided, we'll refer to that to do the lookups
|
35
|
+
# and ensure that return the collection
|
36
|
+
collection = instance_exec(options[:operator], options[:value], collection, &filter[:block])
|
37
|
+
else
|
38
|
+
# If no block is provided, we'll fall back to Active Record like where
|
39
|
+
# lookups on the original collection.
|
40
|
+
case options[:operator]
|
41
|
+
when :eq
|
42
|
+
collection = collection.where("#{key} = ?", options[:value].to_s)
|
43
|
+
when :not_eq
|
44
|
+
collection = collection.where("#{key} != ?", options[:value].to_s)
|
45
|
+
when :starts_with
|
46
|
+
collection = collection.where("#{key} LIKE ?", "#{options[:value].to_s}%")
|
47
|
+
when :ends_with
|
48
|
+
collection = collection.where("#{key} LIKE ?", "%#{options[:value].to_s}")
|
49
|
+
when :gt
|
50
|
+
collection = collection.where("#{key} > ?", options[:value].to_s)
|
51
|
+
when :gte
|
52
|
+
collection = collection.where("#{key} >= ?", options[:value].to_s)
|
53
|
+
when :lt
|
54
|
+
collection = collection.where("#{key} < ?", options[:value].to_s)
|
55
|
+
when :lte
|
56
|
+
collection = collection.where("#{key} <= ?", options[:value].to_s)
|
57
|
+
when :in, :not_in
|
58
|
+
# For checking with arrays, we must make sure the user has sent us
|
59
|
+
# an array otherwise we'll raise an error.
|
60
|
+
unless options[:value].is_a?(Array)
|
61
|
+
error 'FilterError', :issue_code => "ArrayNeeded", :issue_message => "An array value is needed for '#{key}' for in/not_in operator"
|
62
|
+
end
|
63
|
+
values = options[:value].map(&:to_s)
|
64
|
+
collection = options[:operator] == :in ? collection.where(key => values) : collection.where.not(key => values)
|
65
|
+
else
|
66
|
+
error 'FilterError', :issue_code => "UnsupportedOperator", :issue_message => "The operator '#{options[:operator]}' is not supported."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
else
|
70
|
+
# Raise an error if the attribute has been provided by the consumer
|
71
|
+
# that isn't supported by the action.
|
72
|
+
error 'FilterError', :issue_code => "UnsupportedAttribute", :issue_message => "The '#{key}' attribute is not supported for filtering on this action."
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
set_flag :filters, filters_flags
|
77
|
+
instance_exec(collection, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -7,7 +7,7 @@ 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, code_or_message, message = nil)
|
10
|
+
def error(type, code_or_message = nil, message = nil)
|
11
11
|
case type
|
12
12
|
when :not_found then raise(Moonrope::Errors::NotFound, code_or_message)
|
13
13
|
when :access_denied then raise(Moonrope::Errors::AccessDenied, code_or_message)
|
@@ -16,7 +16,11 @@ module Moonrope
|
|
16
16
|
when :structured_error then structured_error(code_or_message, message)
|
17
17
|
else
|
18
18
|
if type.is_a?(String)
|
19
|
-
|
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
|
20
24
|
else
|
21
25
|
raise Moonrope::Errors::RequestError, code_or_message
|
22
26
|
end
|
@@ -31,7 +35,17 @@ module Moonrope
|
|
31
35
|
# @param additional [Hash] additional data to return with the error
|
32
36
|
#
|
33
37
|
def structured_error(code, message, additional = {})
|
34
|
-
|
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)
|
35
49
|
end
|
36
50
|
|
37
51
|
#
|
@@ -40,12 +54,21 @@ module Moonrope
|
|
40
54
|
def paginate(collection, max_per_page = 60, &block)
|
41
55
|
per_page = params.per_page || 30
|
42
56
|
per_page = max_per_page if per_page < 1 || per_page > max_per_page
|
43
|
-
paginated_results = collection.page(params.page).per(per_page)
|
44
|
-
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}
|
45
59
|
paginated_results.to_a.map do |result|
|
46
60
|
block.call(result)
|
47
61
|
end
|
48
62
|
end
|
49
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
|
+
|
50
73
|
end
|
51
74
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ::Guard
|
2
|
+
class Moonrope < Plugin
|
3
|
+
def initialize(options)
|
4
|
+
super
|
5
|
+
@options = options
|
6
|
+
@options[:source] ||= "api"
|
7
|
+
@options[:destination] ||= ".apidoc"
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
UI.info "Starting Moonrope Watching"
|
12
|
+
end
|
13
|
+
|
14
|
+
def reload
|
15
|
+
stop ; start
|
16
|
+
end
|
17
|
+
|
18
|
+
def run_all
|
19
|
+
generate_moonrope_docs
|
20
|
+
end
|
21
|
+
|
22
|
+
def run_on_modifications(paths)
|
23
|
+
generate_moonrope_docs
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def generate_moonrope_docs
|
29
|
+
if File.exist?(File.join(@options[:destination], 'moonrope.txt'))
|
30
|
+
system("rm -Rf #{@options[:destination]}/*")
|
31
|
+
end
|
32
|
+
system("bundle exec moonrope #{@options[:source]} #{@options[:destination]}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'moonrope/doc_context'
|
4
|
+
|
5
|
+
module Moonrope
|
6
|
+
class HtmlGenerator
|
7
|
+
|
8
|
+
def initialize(base, template_root_path)
|
9
|
+
@base = base
|
10
|
+
@template_root_path = template_root_path
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :base
|
14
|
+
attr_reader :template_root_path
|
15
|
+
|
16
|
+
def host
|
17
|
+
ENV['MR_HOST']
|
18
|
+
end
|
19
|
+
|
20
|
+
def prefix
|
21
|
+
ENV['MR_PREFIX'] || 'api'
|
22
|
+
end
|
23
|
+
|
24
|
+
def version
|
25
|
+
ENV['MR_VERSION'] || 'v1'
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate(output_path)
|
29
|
+
FileUtils.mkdir_p(output_path)
|
30
|
+
FileUtils.cp_r(File.join(@template_root_path, 'assets'), File.join(output_path, 'assets'))
|
31
|
+
FileUtils.touch(File.join(output_path, 'moonrope.txt'))
|
32
|
+
# Index
|
33
|
+
generate_file(output_path, "index.html", "index")
|
34
|
+
# Controllers
|
35
|
+
@base.controllers.select { |c| c.doc != false }.each do |controller|
|
36
|
+
generate_file(output_path, File.join("controllers", "#{controller.name}.html"), "controller", {:controller => controller})
|
37
|
+
controller.actions.select { |_,a| a.doc != false }.each do |_, action|
|
38
|
+
generate_file(output_path, File.join("controllers", controller.name.to_s, "#{action.name}.html"), "action", {:controller => controller, :action => action})
|
39
|
+
end
|
40
|
+
end
|
41
|
+
# Structures
|
42
|
+
@base.structures.select { |s| s.doc != false }.each do |structure|
|
43
|
+
generate_file(output_path, File.join("structures", "#{structure.name}.html"), "structure", {:structure => structure})
|
44
|
+
end
|
45
|
+
# Authenticators
|
46
|
+
@base.authenticators.values.select { |s| s.doc != false }.each do |authenticator|
|
47
|
+
generate_file(output_path, File.join("authenticators", "#{authenticator.name}.html"), "authenticator", {:authenticator => authenticator})
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def generate_file(root_dir, output_file, template_file, variables = {})
|
55
|
+
file = DocContext.new(self, :html_extensions => true, :welcome_file => 'index', :output_file => output_file, :vars => variables)
|
56
|
+
file_string = file.render(File.join(@template_root_path, "#{template_file}.erb"))
|
57
|
+
layout = DocContext.new(self, :html_extensions => true, :welcome_file => 'index', :output_file => output_file, :vars => {:page_title => file.vars[:page_title], :active_nav =>file.vars[:active_nav], :body => file_string}).render(File.join(@template_root_path, "layout.erb"))
|
58
|
+
path = File.join(root_dir, output_file)
|
59
|
+
FileUtils.mkdir_p(File.dirname(path))
|
60
|
+
File.open(path, 'w') { |f| f.write(layout) }
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/lib/moonrope/param_set.rb
CHANGED
@@ -29,7 +29,7 @@ module Moonrope
|
|
29
29
|
#
|
30
30
|
def _value_for(key)
|
31
31
|
# Get the value from the params and defaults
|
32
|
-
value = (@params[key.to_s]
|
32
|
+
value = @params.has_key?(key.to_s) ? @params[key.to_s] : @defaults[key.to_s]
|
33
33
|
# Ensure that empty strings are actually nil.
|
34
34
|
value = nil if value.is_a?(String) && value.length == 0
|
35
35
|
# Return the value
|
@@ -39,6 +39,16 @@ module Moonrope
|
|
39
39
|
alias_method :[], :_value_for
|
40
40
|
alias_method :method_missing, :_value_for
|
41
41
|
|
42
|
+
#
|
43
|
+
# Set the value for a given param
|
44
|
+
#
|
45
|
+
# @param key [String]
|
46
|
+
# @param value [AnyObject]
|
47
|
+
#
|
48
|
+
def _set_value(name, value)
|
49
|
+
@params[name.to_s] = value
|
50
|
+
end
|
51
|
+
|
42
52
|
#
|
43
53
|
# Set the defaults for the param set
|
44
54
|
#
|
@@ -79,7 +79,7 @@ module Moonrope
|
|
79
79
|
result = request.execute
|
80
80
|
json = result.to_json
|
81
81
|
global_headers['Content-Length'] = json.bytesize.to_s
|
82
|
-
[200, global_headers.merge(result.headers), [
|
82
|
+
[200, global_headers.merge(result.headers), [json]]
|
83
83
|
rescue JSON::ParserError => e
|
84
84
|
[400, global_headers, [{:status => 'invalid-json', :details => e.message}.to_json]]
|
85
85
|
rescue => e
|
data/lib/moonrope/railtie.rb
CHANGED
@@ -1,19 +1,21 @@
|
|
1
|
+
require 'moonrope/doc_server'
|
2
|
+
|
1
3
|
module Moonrope
|
2
4
|
class Railtie < Rails::Railtie
|
3
5
|
|
4
6
|
initializer 'moonrope.initialize' do |app|
|
5
7
|
|
6
8
|
# Initialize a new moonrope base.
|
7
|
-
|
9
|
+
Moonrope::Base.instance = Moonrope::Base.load(Rails.root.join('api'))
|
8
10
|
|
9
11
|
# Set the logger
|
10
12
|
Moonrope.logger = Rails.logger
|
11
13
|
|
12
14
|
# Set the environment to match the Rails environment
|
13
|
-
|
15
|
+
Moonrope::Base.instance.environment = Rails.env.to_s
|
14
16
|
|
15
17
|
# Ensure all request use UTC
|
16
|
-
|
18
|
+
Moonrope::Base.instance.on_request = Proc.new do |base, env|
|
17
19
|
Time.zone = 'UTC'
|
18
20
|
end
|
19
21
|
|
@@ -22,29 +24,44 @@ module Moonrope
|
|
22
24
|
Moonrope::Request.path_regex = app.config.moonrope_request_path_regex
|
23
25
|
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
ActiveSupport.on_load(:active_record) do
|
28
|
+
|
29
|
+
# Catch ActiveRecord::RecordNotFound exception as a standard not-found error
|
30
|
+
Moonrope::Base.instance.register_external_error ActiveRecord::RecordNotFound do |exception, result|
|
28
31
|
result.status = 'not-found'
|
29
32
|
result.data = {:message => exception.message}
|
30
33
|
end
|
31
34
|
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
35
|
+
# Catch ActiveRecord::DeleteRestrictionError and raise an a DeleteRestrictionError
|
36
|
+
Moonrope::Base.instance.register_external_error ActiveRecord::DeleteRestrictionError do |exception, result|
|
37
|
+
result.status = 'error'
|
38
|
+
result.data = {:code => "DeleteRestrictionError", :message => "Object could not be deleted due to dependency"}
|
39
|
+
if exception.message =~ /([\w\-]+)\z/
|
40
|
+
result.data[:dependency] = $1
|
39
41
|
end
|
40
42
|
end
|
43
|
+
|
44
|
+
# Catch ActiveRecord::RecordInvalid and raise an a ValidationError
|
45
|
+
Moonrope::Base.instance.register_external_error ActiveRecord::RecordInvalid do |exception, result|
|
46
|
+
result.status = 'error'
|
47
|
+
errors = exception.record.errors.respond_to?(:to_api_hash) ? exception.record.errors.to_api_hash : exception.record.errors
|
48
|
+
result.data = {:code => "ValidationError", :message => "Object could not be saved due to a validation error", :errors => errors}
|
49
|
+
end
|
50
|
+
|
41
51
|
end
|
42
52
|
|
53
|
+
# Insert the documentation middleware
|
54
|
+
app.middleware.use(
|
55
|
+
Moonrope::DocServer,
|
56
|
+
Moonrope::Base.instance,
|
57
|
+
:reload_on_each_request => !app.config.cache_classes
|
58
|
+
)
|
59
|
+
|
43
60
|
# Insert the Moonrope middleware into the application's middleware
|
44
61
|
# stack (at the bottom).
|
45
62
|
app.middleware.use(
|
46
63
|
Moonrope::RackMiddleware,
|
47
|
-
|
64
|
+
Moonrope::Base.instance,
|
48
65
|
:reload_on_each_request => !app.config.cache_classes
|
49
66
|
)
|
50
67
|
|
data/lib/moonrope/request.rb
CHANGED
@@ -3,10 +3,16 @@ module Moonrope
|
|
3
3
|
|
4
4
|
class << self
|
5
5
|
attr_accessor :path_regex
|
6
|
+
attr_accessor :path_prefix
|
6
7
|
|
7
8
|
# @return [Regex] the regex which should be matched for all API requests
|
8
9
|
def path_regex
|
9
|
-
@path_regex ||= /\A
|
10
|
+
@path_regex ||= /\A\/#{path_prefix}([\w\/\-\.]+)?/
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Regex] the initial path of the prefix to be matched
|
14
|
+
def path_prefix
|
15
|
+
@path_prefix ||= /api\//
|
10
16
|
end
|
11
17
|
end
|
12
18
|
|
@@ -17,7 +23,7 @@ module Moonrope
|
|
17
23
|
# @return [String] the name of the action which was requested
|
18
24
|
attr_reader :action_name
|
19
25
|
# @return [Object] the authenticated user
|
20
|
-
attr_reader :
|
26
|
+
attr_reader :identity
|
21
27
|
|
22
28
|
#
|
23
29
|
# Initialize a new Moonrope::Request
|
@@ -80,25 +86,30 @@ module Moonrope
|
|
80
86
|
# @return [Moonrope::ActionResult]
|
81
87
|
#
|
82
88
|
def execute
|
83
|
-
eval_env = EvalEnvironment.new(@base, self)
|
84
|
-
|
89
|
+
eval_env = EvalEnvironment.new(@base, self, action)
|
90
|
+
|
91
|
+
if action.authenticator_to_use.is_a?(Moonrope::Authenticator)
|
85
92
|
result = action.convert_errors_to_action_result do
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
93
|
+
if block = action.authenticator_to_use.lookup
|
94
|
+
@identity = eval_env.instance_eval(&block)
|
95
|
+
end
|
96
|
+
|
97
|
+
unless action.check_access(eval_env) == true
|
98
|
+
if rule = action.authenticator_to_use.rules[action.access_rule_to_use]
|
99
|
+
eval_env.structured_error(rule[:error_code], rule[:description], :action => action.name, :controller => controller.name)
|
100
|
+
else
|
101
|
+
eval_env.structured_error("NotPermitted", "You are not permitted to access #{controller.name}/#{action.name}", :action => action.name, :controller => controller.name)
|
92
102
|
end
|
93
103
|
end
|
94
104
|
end
|
95
105
|
|
96
106
|
if result.is_a?(Moonrope::ActionResult)
|
97
|
-
# If we already have a result, we should return it and no longer execute
|
98
|
-
# this request.
|
99
107
|
return result
|
100
108
|
end
|
109
|
+
elsif action.authenticator_to_use == :not_found
|
110
|
+
raise Moonrope::Errors::MissingAuthenticator, "Wanted to use authenticator that was not defined."
|
101
111
|
end
|
112
|
+
|
102
113
|
action.execute(eval_env)
|
103
114
|
end
|
104
115
|
|
@@ -132,7 +143,7 @@ module Moonrope
|
|
132
143
|
# @return [Boolean]
|
133
144
|
#
|
134
145
|
def anonymous?
|
135
|
-
|
146
|
+
identity.nil?
|
136
147
|
end
|
137
148
|
|
138
149
|
#
|
@@ -141,7 +152,7 @@ module Moonrope
|
|
141
152
|
# @return [Boolean]
|
142
153
|
#
|
143
154
|
def authenticated?
|
144
|
-
!(
|
155
|
+
!(identity.nil? || identity == false)
|
145
156
|
end
|
146
157
|
|
147
158
|
#
|