moonrope 1.4.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
#
|