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,42 @@
|
|
|
1
|
+
require 'moonrope/dsl/authenticator_dsl'
|
|
2
|
+
|
|
3
|
+
module Moonrope
|
|
4
|
+
class Authenticator
|
|
5
|
+
|
|
6
|
+
def initialize(name, &block)
|
|
7
|
+
@name = name
|
|
8
|
+
@headers = {}
|
|
9
|
+
@errors = {}
|
|
10
|
+
@rules = {}
|
|
11
|
+
if block_given?
|
|
12
|
+
dsl = Moonrope::DSL::AuthenticatorDSL.new(self)
|
|
13
|
+
dsl.instance_eval(&block)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [Symbol] the name of the authenticator
|
|
18
|
+
attr_reader :name
|
|
19
|
+
|
|
20
|
+
# @return [String] the friendly name for the authenticator
|
|
21
|
+
attr_accessor :friendly_name
|
|
22
|
+
|
|
23
|
+
# @return [String] the description for the authenticator
|
|
24
|
+
attr_accessor :description
|
|
25
|
+
|
|
26
|
+
# @return [Proc] the lookup block
|
|
27
|
+
attr_accessor :lookup
|
|
28
|
+
|
|
29
|
+
# @return [Hash] the headers that this authenticator uses
|
|
30
|
+
attr_reader :headers
|
|
31
|
+
|
|
32
|
+
# @return [Hash] the errors this authenticator can raise
|
|
33
|
+
attr_reader :errors
|
|
34
|
+
|
|
35
|
+
# @return [Hash] the rules this authenticator provides
|
|
36
|
+
attr_reader :rules
|
|
37
|
+
|
|
38
|
+
# @return [Bool] whether or not the action should be documented
|
|
39
|
+
attr_accessor :doc
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/moonrope/base.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'moonrope/dsl/base_dsl'
|
|
2
|
+
|
|
1
3
|
module Moonrope
|
|
2
4
|
class Base
|
|
3
5
|
|
|
@@ -14,6 +16,11 @@ module Moonrope
|
|
|
14
16
|
api
|
|
15
17
|
end
|
|
16
18
|
|
|
19
|
+
class << self
|
|
20
|
+
# @return [Moonrope::Base] return a global instance
|
|
21
|
+
attr_accessor :instance
|
|
22
|
+
end
|
|
23
|
+
|
|
17
24
|
# @return [Array] the array of defined structures
|
|
18
25
|
attr_reader :structures
|
|
19
26
|
|
|
@@ -26,11 +33,11 @@ module Moonrope
|
|
|
26
33
|
# @return [Moonrope::DSL::BaseDSL] the base DSL
|
|
27
34
|
attr_accessor :dsl
|
|
28
35
|
|
|
29
|
-
# @return [
|
|
30
|
-
attr_accessor :
|
|
36
|
+
# @return [Hash] authenticators
|
|
37
|
+
attr_accessor :authenticators
|
|
31
38
|
|
|
32
|
-
# @return [
|
|
33
|
-
attr_accessor :
|
|
39
|
+
# @return [Hash] global shared actions
|
|
40
|
+
attr_accessor :shared_actions
|
|
34
41
|
|
|
35
42
|
# @return [Array] the array of directories to load from (if relevant)
|
|
36
43
|
attr_accessor :load_directories
|
|
@@ -41,6 +48,9 @@ module Moonrope
|
|
|
41
48
|
# @return [Proc] a proc to execute before every request
|
|
42
49
|
attr_accessor :on_request
|
|
43
50
|
|
|
51
|
+
# @return [Boolean] is SSL forced?
|
|
52
|
+
attr_accessor :force_ssl
|
|
53
|
+
|
|
44
54
|
#
|
|
45
55
|
# Initialize a new instance of the Moonrope::Base
|
|
46
56
|
#
|
|
@@ -54,6 +64,24 @@ module Moonrope
|
|
|
54
64
|
@dsl.instance_eval(&block) if block_given?
|
|
55
65
|
end
|
|
56
66
|
|
|
67
|
+
#
|
|
68
|
+
# Make a new base based on configuration
|
|
69
|
+
#
|
|
70
|
+
def copy_from(other)
|
|
71
|
+
@environment = other.environment
|
|
72
|
+
@load_directories = other.load_directories
|
|
73
|
+
@on_request = other.on_request
|
|
74
|
+
other.request_callbacks.each { |block| self.register_request_callback(&block) }
|
|
75
|
+
other.request_error_callbacks.each { |block| self.register_request_error_callback(&block) }
|
|
76
|
+
other.external_errors.each { |error, block| self.register_external_error(error, &block) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def copy
|
|
80
|
+
new_base = self.class.new
|
|
81
|
+
new_base.copy_from(self)
|
|
82
|
+
new_base
|
|
83
|
+
end
|
|
84
|
+
|
|
57
85
|
#
|
|
58
86
|
# Reset the whole base to contain no data.
|
|
59
87
|
#
|
|
@@ -61,7 +89,8 @@ module Moonrope
|
|
|
61
89
|
@structures = []
|
|
62
90
|
@controllers = []
|
|
63
91
|
@helpers = @helpers.is_a?(Array) ? @helpers.select { |h| h.options[:unloadable] == false } : []
|
|
64
|
-
@
|
|
92
|
+
@authenticators = {}
|
|
93
|
+
@shared_actions = {}
|
|
65
94
|
@default_access = nil
|
|
66
95
|
end
|
|
67
96
|
|
|
@@ -92,7 +121,17 @@ module Moonrope
|
|
|
92
121
|
#
|
|
93
122
|
def load_directory(directory)
|
|
94
123
|
if File.exist?(directory)
|
|
95
|
-
|
|
124
|
+
@loaded_files = []
|
|
125
|
+
Dir[
|
|
126
|
+
"#{directory}/structures/**/*.rb",
|
|
127
|
+
"#{directory}/shared_actions/**/*.rb",
|
|
128
|
+
"#{directory}/controllers/**/*.rb",
|
|
129
|
+
"#{directory}/helpers/**/*.rb",
|
|
130
|
+
"#{directory}/authenticators/**/*.rb",
|
|
131
|
+
"#{directory}/*.rb",
|
|
132
|
+
].each do |filename|
|
|
133
|
+
next if @loaded_files.include?(filename)
|
|
134
|
+
@loaded_files << filename
|
|
96
135
|
self.dsl.instance_eval(File.read(filename), filename)
|
|
97
136
|
end
|
|
98
137
|
true
|
|
@@ -196,5 +235,27 @@ module Moonrope
|
|
|
196
235
|
@request_error_callbacks ||= []
|
|
197
236
|
end
|
|
198
237
|
|
|
238
|
+
#
|
|
239
|
+
# Set a block which will be executed whenever a request is received by moonrope.
|
|
240
|
+
#
|
|
241
|
+
#
|
|
242
|
+
def register_request_callback(&block)
|
|
243
|
+
request_callbacks << block
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
#
|
|
247
|
+
# Return an array of request callbacks
|
|
248
|
+
#
|
|
249
|
+
def request_callbacks
|
|
250
|
+
@request_callbacks ||= []
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
#
|
|
254
|
+
# Should SSL be forced?
|
|
255
|
+
#
|
|
256
|
+
def force_ssl?
|
|
257
|
+
@force_ssl || false
|
|
258
|
+
end
|
|
259
|
+
|
|
199
260
|
end
|
|
200
261
|
end
|
data/lib/moonrope/controller.rb
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
require 'moonrope/dsl/controller_dsl'
|
|
2
|
+
|
|
1
3
|
module Moonrope
|
|
2
4
|
class Controller
|
|
3
5
|
|
|
4
|
-
attr_accessor :name, :actions, :
|
|
6
|
+
attr_accessor :name, :actions, :befores, :friendly_name, :description, :doc, :authenticator, :access_rule, :shared_actions
|
|
5
7
|
attr_reader :base, :dsl
|
|
6
8
|
|
|
7
9
|
#
|
|
@@ -15,7 +17,7 @@ module Moonrope
|
|
|
15
17
|
@base = base
|
|
16
18
|
@name = name
|
|
17
19
|
@actions = {}
|
|
18
|
-
@
|
|
20
|
+
@shared_actions = {}
|
|
19
21
|
@befores = []
|
|
20
22
|
@dsl = Moonrope::DSL::ControllerDSL.new(self)
|
|
21
23
|
@dsl.instance_eval(&block) if block_given?
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Moonrope
|
|
2
|
+
class DocContext
|
|
3
|
+
|
|
4
|
+
attr_reader :vars
|
|
5
|
+
|
|
6
|
+
def initialize(generator, options = {})
|
|
7
|
+
@generator = generator
|
|
8
|
+
@vars = options.delete(:vars) || {}
|
|
9
|
+
@options = options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def set_page_title(title)
|
|
13
|
+
@vars[:page_title] = title
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def set_active_nav(nav)
|
|
17
|
+
@vars[:active_nav] = nav
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def base
|
|
21
|
+
@generator.base
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def host
|
|
25
|
+
@generator.host
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def prefix
|
|
29
|
+
@generator.prefix
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def version
|
|
33
|
+
@generator.version
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def method_missing(name)
|
|
37
|
+
if @vars.has_key?(name.to_sym)
|
|
38
|
+
@vars[name.to_sym]
|
|
39
|
+
else
|
|
40
|
+
super
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def git_version
|
|
45
|
+
ENV["VDT_VERSION"] ||
|
|
46
|
+
(`git rev-parse HEAD`.strip rescue nil)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def asset_path(file)
|
|
50
|
+
path("assets/" + file)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def full_prefix
|
|
54
|
+
"#{host}/#{prefix}/#{version}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def path(file)
|
|
58
|
+
depth = ((@options[:output_file] || '').split('/').size - 1).times.map { "../" }.join
|
|
59
|
+
if file == :root
|
|
60
|
+
file = depth + (@options[:welcome_file] || "welcome")
|
|
61
|
+
else
|
|
62
|
+
file = depth + file
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if @options[:html_extensions] && !(file =~ /\.[a-z]+\z/)
|
|
66
|
+
file = "#{file}.html"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
file
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def render(template_file)
|
|
73
|
+
ERB.new(File.read(template_file), nil, '-').result(binding)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def partial(name, attributes = {})
|
|
77
|
+
erb = self.class.new(@generator, @options.merge(:vars => attributes))
|
|
78
|
+
erb.render(File.join(@generator.template_root_path, "_#{name}.erb"))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def friendly_type(type)
|
|
82
|
+
if type.is_a?(Symbol)
|
|
83
|
+
type.to_s.capitalize
|
|
84
|
+
else
|
|
85
|
+
type.to_s
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def humanize(string)
|
|
90
|
+
string.to_s.gsub(/\_/, ' ')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require 'moonrope/doc_context'
|
|
2
|
+
|
|
3
|
+
module Moonrope
|
|
4
|
+
class DocServer
|
|
5
|
+
|
|
6
|
+
CONTENT_TYPES = {
|
|
7
|
+
'css' => 'text/css',
|
|
8
|
+
'js' => 'text/javascript',
|
|
9
|
+
'svg' => 'image/svg+xml'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
#
|
|
14
|
+
# Set the default path regex which should be matched for requests for
|
|
15
|
+
# API docmentation. By default, this is /api/docs/.
|
|
16
|
+
#
|
|
17
|
+
def path_regex
|
|
18
|
+
@path_regex ||= /\A\/#{Moonrope::Request.path_prefix}docs\/([\w\.]+)\/?([\w\/\-\.]+)?/
|
|
19
|
+
end
|
|
20
|
+
attr_writer :path_regex
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(app, base, options = {})
|
|
24
|
+
@app = app
|
|
25
|
+
@base = base
|
|
26
|
+
@options = options
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
attr_reader :base
|
|
30
|
+
|
|
31
|
+
class Generator
|
|
32
|
+
def initialize(base, options = {})
|
|
33
|
+
@base = base
|
|
34
|
+
@options = options
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
attr_reader :base
|
|
38
|
+
|
|
39
|
+
def template_root_path
|
|
40
|
+
File.expand_path("../../../templates/basic", __FILE__)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def host
|
|
44
|
+
@options[:host]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def prefix
|
|
48
|
+
@options[:prefix]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def version
|
|
52
|
+
@options[:version]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def generate_file(output_file, template_file, variables = {})
|
|
56
|
+
# Generate the page for the requested template with the given variables
|
|
57
|
+
file = DocContext.new(self, :output_file => output_file, :vars => variables)
|
|
58
|
+
file_string = file.render(File.join(template_root_path, "#{template_file}.erb"))
|
|
59
|
+
# Generate the final page within the layout
|
|
60
|
+
DocContext.new(self, :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"))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def call(env)
|
|
65
|
+
if env['PATH_INFO'] =~ self.class.path_regex
|
|
66
|
+
version = $1
|
|
67
|
+
doc_path = $2
|
|
68
|
+
request = Rack::Request.new(env)
|
|
69
|
+
generator = Generator.new(@base, :host => "#{request.scheme}://#{request.host_with_port}", :version => version, :prefix => env['PATH_INFO'].split('/')[1])
|
|
70
|
+
|
|
71
|
+
if @options[:reload_on_each_request]
|
|
72
|
+
@base.load
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
file = nil
|
|
76
|
+
content_type = nil
|
|
77
|
+
|
|
78
|
+
case doc_path
|
|
79
|
+
when nil, ""
|
|
80
|
+
return [302, {'Location' => "#{env['PATH_INFO']}/welcome"}, ['']]
|
|
81
|
+
when /\Awelcome\z/, /\Aindex\.html\z/
|
|
82
|
+
file = generator.generate_file(doc_path, 'index')
|
|
83
|
+
when /\Acontrollers\/(\w+)(\.html)?\z/
|
|
84
|
+
if controller = @base.controller($1.to_sym)
|
|
85
|
+
file = generator.generate_file(doc_path, 'controller', :controller => controller)
|
|
86
|
+
end
|
|
87
|
+
when /\Acontrollers\/(\w+)\/(\w+)(\.html)?\z/
|
|
88
|
+
if controller = @base.controller($1.to_sym)
|
|
89
|
+
if action = controller.action($2.to_sym)
|
|
90
|
+
file = generator.generate_file(doc_path, 'action', :controller => controller, :action => action)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
when /\Astructures\/(\w+)(\.html)?\z/
|
|
94
|
+
if structure = @base.structure($1.to_sym)
|
|
95
|
+
file = generator.generate_file(doc_path, 'structure', :structure => structure)
|
|
96
|
+
end
|
|
97
|
+
when /\Aauthenticators\/(\w+)(\.html)?\z/
|
|
98
|
+
if authenticator = @base.authenticators[$1.to_sym]
|
|
99
|
+
file = generator.generate_file(doc_path, 'authenticator', :authenticator => authenticator)
|
|
100
|
+
end
|
|
101
|
+
when /\Aassets\/([\w]+)\.([a-z]+)\z/
|
|
102
|
+
path = File.join(generator.template_root_path, 'assets', "#{$1}.#{$2}")
|
|
103
|
+
if File.exist?(path)
|
|
104
|
+
file = File.read(path)
|
|
105
|
+
content_type = CONTENT_TYPES[$2] || 'text/plain'
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if file
|
|
110
|
+
[200, {
|
|
111
|
+
'Content-Type' => content_type || 'text/html',
|
|
112
|
+
'Content-Length' => file.bytesize.to_s},
|
|
113
|
+
[file]]
|
|
114
|
+
else
|
|
115
|
+
[404, {}, ['Not found']]
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
return @app.call(env)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
require 'moonrope/errors'
|
|
2
|
+
require 'moonrope/dsl/filterable_dsl'
|
|
3
|
+
|
|
1
4
|
module Moonrope
|
|
2
5
|
module DSL
|
|
3
6
|
class ActionDSL
|
|
@@ -11,6 +14,18 @@ module Moonrope
|
|
|
11
14
|
@action = action
|
|
12
15
|
end
|
|
13
16
|
|
|
17
|
+
#
|
|
18
|
+
# Set the title for the action
|
|
19
|
+
#
|
|
20
|
+
# title "List all users"
|
|
21
|
+
#
|
|
22
|
+
# @param value [String]
|
|
23
|
+
# @return [void]
|
|
24
|
+
#
|
|
25
|
+
def title(value)
|
|
26
|
+
@action.title = value
|
|
27
|
+
end
|
|
28
|
+
|
|
14
29
|
#
|
|
15
30
|
# Set the description for the action
|
|
16
31
|
#
|
|
@@ -23,6 +38,14 @@ module Moonrope
|
|
|
23
38
|
@action.description = value
|
|
24
39
|
end
|
|
25
40
|
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# Set this action so that it isn't documented
|
|
44
|
+
#
|
|
45
|
+
def no_doc!
|
|
46
|
+
@action.doc = false
|
|
47
|
+
end
|
|
48
|
+
|
|
26
49
|
#
|
|
27
50
|
# Add a new param to the action's param set.
|
|
28
51
|
#
|
|
@@ -33,27 +56,94 @@ module Moonrope
|
|
|
33
56
|
# @param options_if_description [Hash] a hash of additional options if a description was provided
|
|
34
57
|
# @return [void]
|
|
35
58
|
#
|
|
36
|
-
def param(name, description_or_options = {}, options_if_description = {})
|
|
59
|
+
def param(name, description_or_options = {}, options_if_description = {}, &block)
|
|
37
60
|
if description_or_options.is_a?(String)
|
|
38
61
|
options = options_if_description.merge(:description => description_or_options)
|
|
39
62
|
else
|
|
40
63
|
options = description_or_options
|
|
41
64
|
end
|
|
65
|
+
|
|
66
|
+
options[:from_structure] ||= @from_structure if @from_structure
|
|
67
|
+
|
|
68
|
+
if structure = options[:from_structure]
|
|
69
|
+
if @action.controller && structure = @action.controller.base.structure(structure)
|
|
70
|
+
if attribute = structure.attribute(name)
|
|
71
|
+
options[:description] ||= attribute.description
|
|
72
|
+
options[:type] ||= attribute.value_type
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
options[:apply] = block if block_given?
|
|
78
|
+
options[:from_shared_action] = @within_shared_action.dup if @within_shared_action
|
|
42
79
|
@action.params[name] = options
|
|
43
80
|
end
|
|
44
81
|
|
|
45
82
|
#
|
|
46
|
-
#
|
|
83
|
+
# Specifies that all params within this block should be marked as being from
|
|
84
|
+
# a given structure
|
|
47
85
|
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
86
|
+
# from_structure :user do
|
|
87
|
+
# param :username
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
# @param name [Symbol] the name of the structure
|
|
51
91
|
#
|
|
52
|
-
|
|
92
|
+
def from_structure(name, &block)
|
|
93
|
+
@from_structure = name
|
|
94
|
+
self.instance_eval(&block)
|
|
95
|
+
ensure
|
|
96
|
+
@from_structure = nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#
|
|
100
|
+
# Add a new error to the actions' errors
|
|
101
|
+
#
|
|
102
|
+
# error "NoUnitFound", "The unit with given {{id}} could not be found"
|
|
103
|
+
#
|
|
104
|
+
# @param name [String] the name of the error
|
|
105
|
+
# @param description [String] a description of the error
|
|
53
106
|
# @return [void]
|
|
54
107
|
#
|
|
55
|
-
def
|
|
56
|
-
@action.
|
|
108
|
+
def error(name, description, options = {})
|
|
109
|
+
@action.errors[name] = options.merge(:description => description, :from_share => @within_share)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
#
|
|
113
|
+
# Sets the type of return value that is expected from a successful call
|
|
114
|
+
# to this API action.
|
|
115
|
+
#
|
|
116
|
+
# returns :array, :structure => :user
|
|
117
|
+
#
|
|
118
|
+
# @param type [Symbol] the type of object that will be returend
|
|
119
|
+
# @param options [Hash] further options about the returned value
|
|
120
|
+
# @return [void]
|
|
121
|
+
#
|
|
122
|
+
def returns(type, options = {})
|
|
123
|
+
@action.returns = options.merge(:type => type)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
#
|
|
127
|
+
# Sets the name of the authenticator to use for this action
|
|
128
|
+
#
|
|
129
|
+
# @param name [Symbol] the name of the authenticator
|
|
130
|
+
#
|
|
131
|
+
def authenticator(name)
|
|
132
|
+
@action.authenticator = name
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
#
|
|
136
|
+
# Sets the name of the access rule to use for this action
|
|
137
|
+
#
|
|
138
|
+
# @param name [Symbol] the name of the authenticator
|
|
139
|
+
#
|
|
140
|
+
def access_rule(name)
|
|
141
|
+
if name.is_a?(Hash)
|
|
142
|
+
authenticator name.first[0]
|
|
143
|
+
access_rule name.first[1]
|
|
144
|
+
else
|
|
145
|
+
@action.access_rule = name
|
|
146
|
+
end
|
|
57
147
|
end
|
|
58
148
|
|
|
59
149
|
#
|
|
@@ -67,7 +157,67 @@ module Moonrope
|
|
|
67
157
|
# @return [void]
|
|
68
158
|
#
|
|
69
159
|
def action(&block)
|
|
70
|
-
@action.
|
|
160
|
+
@action.actions << block
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
#
|
|
164
|
+
# Specify that this action will be returning paginated data. Sets up the
|
|
165
|
+
# parameters for the action as appropriate.
|
|
166
|
+
#
|
|
167
|
+
def paginated(options = {})
|
|
168
|
+
@action.traits << :paginated
|
|
169
|
+
param :page, "The page number", :type => Integer, :required => true, :default => options[:page] || 1
|
|
170
|
+
param :per_page, "The number of items to return per page", :type => Integer, :required => true, :default => options[:per_page] || 30
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
#
|
|
174
|
+
# Specify that this action will return data sorted by user provided data.
|
|
175
|
+
#
|
|
176
|
+
def sortable(*fields)
|
|
177
|
+
if fields.empty?
|
|
178
|
+
raise Moonrope::Errors::Error, "You must specify at least one field when calling 'sortable'"
|
|
179
|
+
else
|
|
180
|
+
if fields.first.is_a?(Hash)
|
|
181
|
+
default_order = fields.first.first[1].to_s
|
|
182
|
+
fields[0] = fields.first.first[0]
|
|
183
|
+
else
|
|
184
|
+
default_order = 'asc'
|
|
185
|
+
end
|
|
186
|
+
@action.traits << :sortable
|
|
187
|
+
param :sort_by, "The field to sort by", :type => String, :required => true, :default => fields[0].to_s, :options => fields.map(&:to_s)
|
|
188
|
+
param :order, "The direction to order units by", :type => String, :required => true, :default => default_order, :options => ["asc", "desc"]
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
#
|
|
193
|
+
# Specify that this action will return data which can be filtered by specifying
|
|
194
|
+
# certain parameters on a filter parameter
|
|
195
|
+
#
|
|
196
|
+
def filterable(&block)
|
|
197
|
+
if @action.errors['FilterError'].nil?
|
|
198
|
+
error 'FilterError', "An error has occurred while processing filters for this action", :attributes => {:issue_code => "A more specific issue code", :issue_message => "A more specific message about the issue"}
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if @action.params[:filters].nil?
|
|
202
|
+
param :filters, "A hash of filters to apply to results", :type => Hash, :default => {}
|
|
203
|
+
end
|
|
204
|
+
dsl = FilterableDSL.new(@action)
|
|
205
|
+
dsl.instance_eval(&block)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
#
|
|
209
|
+
# Include any block from the controller shares
|
|
210
|
+
#
|
|
211
|
+
def use(name, options = {})
|
|
212
|
+
if block = (@action.controller.shared_actions[name] || @action.controller.base.shared_actions[name])
|
|
213
|
+
@within_shared_action ||= []
|
|
214
|
+
@within_shared_action << name
|
|
215
|
+
self.instance_exec(options, &block)
|
|
216
|
+
else
|
|
217
|
+
raise Moonrope::Errors::InvalidSharedAction, "Invalid share name #{name}"
|
|
218
|
+
end
|
|
219
|
+
ensure
|
|
220
|
+
@within_shared_action.delete(name) if @within_shared_action
|
|
71
221
|
end
|
|
72
222
|
|
|
73
223
|
end
|