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.
- checksums.yaml +5 -5
- 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/lib/moonrope.rb +5 -4
- data/lib/moonrope/action.rb +170 -40
- data/lib/moonrope/authenticator.rb +42 -0
- data/lib/moonrope/base.rb +67 -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 +35 -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 +28 -2
- data/lib/moonrope/errors.rb +13 -0
- data/lib/moonrope/eval_environment.rb +82 -3
- data/lib/moonrope/eval_helpers.rb +47 -8
- data/lib/moonrope/eval_helpers/filter_helper.rb +82 -0
- 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 +66 -37
- data/lib/moonrope/railtie.rb +31 -14
- data/lib/moonrope/request.rb +43 -15
- data/lib/moonrope/structure.rb +100 -18
- data/lib/moonrope/structure_attribute.rb +39 -0
- data/lib/moonrope/version.rb +1 -1
- 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 +157 -0
- data/templates/basic/authenticator.erb +52 -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 +131 -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 +398 -0
- metadata +71 -56
|
@@ -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
|
|
@@ -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
|
#
|
|
@@ -25,23 +25,6 @@ module Moonrope
|
|
|
25
25
|
#
|
|
26
26
|
def call(env)
|
|
27
27
|
if env['PATH_INFO'] =~ Moonrope::Request.path_regex
|
|
28
|
-
|
|
29
|
-
if @options[:reload_on_each_request]
|
|
30
|
-
@base.load
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
# Call the on request block if one has been defined for the base.
|
|
35
|
-
#
|
|
36
|
-
if @base.on_request.is_a?(Proc)
|
|
37
|
-
@base.on_request.call(@base, env)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
# Create a new request object
|
|
42
|
-
#
|
|
43
|
-
request = @base.request(env, $1)
|
|
44
|
-
|
|
45
28
|
#
|
|
46
29
|
# Set some global headers which are always returned
|
|
47
30
|
#
|
|
@@ -65,6 +48,38 @@ module Moonrope
|
|
|
65
48
|
#
|
|
66
49
|
global_headers['Content-Type'] = 'application/json'
|
|
67
50
|
|
|
51
|
+
#
|
|
52
|
+
# Reload if needed
|
|
53
|
+
#
|
|
54
|
+
if @options[:reload_on_each_request]
|
|
55
|
+
@base = @base.copy
|
|
56
|
+
begin
|
|
57
|
+
@base.load
|
|
58
|
+
rescue => e
|
|
59
|
+
return generate_error_triplet(@base, nil, e, global_headers)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
#
|
|
64
|
+
# Create a new request object
|
|
65
|
+
#
|
|
66
|
+
request = base.request(env, $1)
|
|
67
|
+
|
|
68
|
+
#
|
|
69
|
+
# If force SSL is enabled, don't allow requests to proceed if they're
|
|
70
|
+
# not SSL
|
|
71
|
+
#
|
|
72
|
+
if base.force_ssl? && !request.ssl?
|
|
73
|
+
return [400, global_headers, [{:status => 'http-not-supported', :message => "Non-secure HTTP connections are not supported. Requests should be made using https:// rather than http://."}.to_json]]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#
|
|
77
|
+
# Call the on request block if one has been defined for the base.
|
|
78
|
+
#
|
|
79
|
+
if base.on_request.is_a?(Proc)
|
|
80
|
+
base.on_request.call(base, env)
|
|
81
|
+
end
|
|
82
|
+
|
|
68
83
|
#
|
|
69
84
|
# Check the request is valid
|
|
70
85
|
#
|
|
@@ -78,30 +93,22 @@ module Moonrope
|
|
|
78
93
|
begin
|
|
79
94
|
result = request.execute
|
|
80
95
|
json = result.to_json
|
|
81
|
-
global_headers['Content-Length'] = json.bytesize.to_s
|
|
82
|
-
[200, global_headers.merge(result.headers), [result.to_json]]
|
|
83
|
-
rescue JSON::ParserError => e
|
|
84
|
-
[400, global_headers, [{:status => 'invalid-json', :details => e.message}.to_json]]
|
|
85
|
-
rescue => e
|
|
86
|
-
Moonrope.logger.info e.class
|
|
87
|
-
Moonrope.logger.info e.message
|
|
88
|
-
Moonrope.logger.info e.backtrace.join("\n")
|
|
89
|
-
|
|
90
|
-
response = {:status => 'internal-server-error'}
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
end
|
|
97
|
+
global_headers['Content-Length'] = json.bytesize.to_s
|
|
98
|
+
headers = global_headers.merge(result.headers)
|
|
99
|
+
Moonrope.logger.info "[#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")}] controller=#{request.controller.name} action=#{request.action.name} status=#{result.status} time=#{result.time} ip=#{request.ip} size=#{json.bytesize}"
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
response[:backtrace] = e.backtrace[0,6]
|
|
101
|
+
base.request_callbacks.each do |callback|
|
|
102
|
+
# Call each request callback and provide the request, the result
|
|
103
|
+
# and the raw that's being returned to the user.
|
|
104
|
+
callback.call(request, result, json, headers)
|
|
102
105
|
end
|
|
103
106
|
|
|
104
|
-
[
|
|
107
|
+
[200, headers, [json]]
|
|
108
|
+
rescue JSON::ParserError => e
|
|
109
|
+
[400, global_headers, [{:status => 'invalid-json', :details => e.message}.to_json]]
|
|
110
|
+
rescue => e
|
|
111
|
+
generate_error_triplet(base, request, e, global_headers)
|
|
105
112
|
end
|
|
106
113
|
|
|
107
114
|
else
|
|
@@ -113,5 +120,27 @@ module Moonrope
|
|
|
113
120
|
end
|
|
114
121
|
end
|
|
115
122
|
|
|
123
|
+
def generate_error_triplet(base, request, exception, headers = {})
|
|
124
|
+
Moonrope.logger.info exception.class
|
|
125
|
+
Moonrope.logger.info exception.message
|
|
126
|
+
Moonrope.logger.info exception.backtrace.join("\n")
|
|
127
|
+
|
|
128
|
+
response = {:status => 'internal-server-error'}
|
|
129
|
+
|
|
130
|
+
# Call any request errors which have been registered on the base
|
|
131
|
+
base.request_error_callbacks.each do |callback|
|
|
132
|
+
callback.call(request, exception)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# If in development, return more details about the exception which was raised.
|
|
136
|
+
if base.environment == 'development'
|
|
137
|
+
response[:error] = exception.class.to_s
|
|
138
|
+
response[:message] = exception.message
|
|
139
|
+
response[:backtrace] = exception.backtrace[0,6]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
[500, headers, [response.to_json]]
|
|
143
|
+
end
|
|
144
|
+
|
|
116
145
|
end
|
|
117
146
|
end
|
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
|
|
|
@@ -109,7 +120,7 @@ module Moonrope
|
|
|
109
120
|
#
|
|
110
121
|
def params
|
|
111
122
|
@params ||= begin
|
|
112
|
-
if @env['CONTENT_TYPE']
|
|
123
|
+
if @env['CONTENT_TYPE'] && @env['CONTENT_TYPE'] =~ /\Aapplication\/json(;|\z)/i
|
|
113
124
|
Moonrope::ParamSet.new(rack_request.body.read)
|
|
114
125
|
else
|
|
115
126
|
Moonrope::ParamSet.new(rack_request.params['params'])
|
|
@@ -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,24 @@ module Moonrope
|
|
|
141
152
|
# @return [Boolean]
|
|
142
153
|
#
|
|
143
154
|
def authenticated?
|
|
144
|
-
!(
|
|
155
|
+
!(identity.nil? || identity == false)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
#
|
|
159
|
+
# Return the remote IP
|
|
160
|
+
#
|
|
161
|
+
# @return [String]
|
|
162
|
+
#
|
|
163
|
+
def ip
|
|
164
|
+
rack_request.ip
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
#
|
|
168
|
+
# Is this request on SSL?
|
|
169
|
+
#
|
|
170
|
+
# @return [Boolean]
|
|
171
|
+
def ssl?
|
|
172
|
+
rack_request.ssl?
|
|
145
173
|
end
|
|
146
174
|
|
|
147
175
|
private
|