moonrope 1.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.
@@ -0,0 +1,35 @@
1
+ module Moonrope
2
+ module EvalHelpers
3
+
4
+ #
5
+ # Raise an error.
6
+ #
7
+ # @param type [Symbol] the type of error to raise
8
+ # @param message [String, Hash or Array] options to pass with the error (usually a message)
9
+ #
10
+ def error(type, message)
11
+ case type
12
+ when :not_found then raise(Moonrope::Errors::NotFound, message)
13
+ when :access_denied then raise(Moonrope::Errors::AccessDenied, message)
14
+ when :validation_error then raise(Moonrope::Errors::ValidationError, message)
15
+ when :parameter_error then raise(Moonrope::Errors::ParameterError, message)
16
+ else
17
+ raise Moonrope::Errors::RequestError, message
18
+ end
19
+ end
20
+
21
+ #
22
+ # Return paginated information
23
+ #
24
+ def paginate(collection, max_per_page = 60, &block)
25
+ per_page = params.per_page || 30
26
+ per_page = max_per_page if per_page < 1 || per_page > max_per_page
27
+ paginated_results = collection.page(params.page).per(per_page)
28
+ set_flag :paginated, {:page => params.page, :per_page => per_page, :total_pages => paginated_results.total_pages, :total_records => paginated_results.total_count}
29
+ paginated_results.to_a.map do |result|
30
+ block.call(result)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ module Moonrope
2
+ class Helper
3
+
4
+ # @return [Symbol] the name of the helper
5
+ attr_reader :name
6
+
7
+ # @return [Moonrope::Controller] the controller this helper belongs to
8
+ attr_reader :controller
9
+
10
+ # @return [Proc] the proc to execute
11
+ attr_reader :block
12
+
13
+
14
+ #
15
+ # Initialize a new helper
16
+ #
17
+ # @param name [Symbol] the name of the helper
18
+ # @param controller [Moonrope::Controller] the controller the helper belongs to
19
+ # @yield stores the block for use later
20
+ #
21
+ def initialize(name, controller, &block)
22
+ @name = name
23
+ @controller = controller
24
+ @block = block
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ module Moonrope
2
+ class ParamSet
3
+
4
+ #
5
+ # Initialize a new ParamSet
6
+ #
7
+ # @param params [Hash or String] the initial params. If string, will be parsed through JSON.
8
+ #
9
+ def initialize(params = {})
10
+ @params = (params.is_a?(String) ? JSON.parse(params) : params) || {}
11
+ @defaults = {}
12
+ end
13
+
14
+ #
15
+ # Return the value for the given key
16
+ #
17
+ # @param key [String] the key to lookup
18
+ # @return [Object] the value
19
+ #
20
+ def _value_for(key)
21
+ # Get the value from the params and defaults
22
+ value = (@params[key.to_s] || @defaults[key.to_s])
23
+ # Ensure that empty strings are actually nil.
24
+ value = nil if value.is_a?(String) && value.length == 0
25
+ # Return the value
26
+ value
27
+ end
28
+
29
+ alias_method :[], :_value_for
30
+ alias_method :method_missing, :_value_for
31
+
32
+ #
33
+ # Set the defaults for the param set
34
+ #
35
+ # @param defaults [Hash]
36
+ # @return [void]
37
+ def _defaults=(defaults)
38
+ if defaults.is_a?(Hash)
39
+ @defaults = defaults
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,73 @@
1
+ module Moonrope
2
+ class RackMiddleware
3
+
4
+ #
5
+ # Initialize a new Moonrope::Rack server
6
+ #
7
+ # @param app [Object] the next Rack application in the stack
8
+ # @param base [Moonrope::Base] the base API to serve
9
+ # @param options [Hash] a hash of options
10
+ #
11
+ #
12
+ def initialize(app, base, options = {})
13
+ @app = app
14
+ @base = base
15
+ @options = options
16
+ end
17
+
18
+ #
19
+ # Make a new request
20
+ #
21
+ # @param env [Hash] a rack environment hash
22
+ # @return [Array] a rack triplet
23
+ #
24
+ def call(env)
25
+ if env['PATH_INFO'] =~ Moonrope::Request::PATH_REGEX
26
+
27
+ if @options[:reload_on_each_request]
28
+ @base.load
29
+ end
30
+
31
+ #
32
+ # Create a new request object
33
+ #
34
+ request = @base.request(env, $1)
35
+
36
+ #
37
+ # Check the request is valid
38
+ #
39
+ unless request.valid?
40
+ return [400, {}, ["Invalid API Request. Must provide a version, controller & action as /api/v1/controller/action."]]
41
+ end
42
+
43
+ global_headers = {}
44
+ global_headers['Content-Type'] = 'application/json'
45
+
46
+ #
47
+ # Execute the request
48
+ #
49
+ begin
50
+ result = request.execute
51
+ json = result.to_json
52
+ global_headers['Content-Length'] = json.bytesize.to_s
53
+ [200, global_headers.merge(result.headers), [result.to_json]]
54
+ rescue JSON::ParserError => e
55
+ [400, global_headers, [{:status => 'invalid-json', :details => e.message}.to_json]]
56
+ rescue => e
57
+ Moonrope.logger.info e.class
58
+ Moonrope.logger.info e.message
59
+ Moonrope.logger.info e.backtrace.join("\n")
60
+ [500, global_headers, [{:status => 'internal-server-error'}.to_json]]
61
+ end
62
+
63
+ else
64
+ if @app && @app.respond_to?(:call)
65
+ @app.call(env)
66
+ else
67
+ [404, {}, ["Non-API request"]]
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,30 @@
1
+ module Moonrope
2
+ class Railtie < Rails::Railtie
3
+
4
+ initializer 'moonrope.initialize' do |app|
5
+
6
+ # Initialize a new moonrope base from the API defined in
7
+ # $RAILS_ROOT/app/api directory.
8
+ moonrope_directory = Rails.root.join('api')
9
+ if File.directory?(moonrope_directory)
10
+ app.config.moonrope = Moonrope::Base.load(moonrope_directory)
11
+ else
12
+ $stderr.puts "Moonrope is installed but there is no API directory at ROOT/app/api."
13
+ next
14
+ end
15
+
16
+ # Set the logger
17
+ Moonrope.logger = Rails.logger
18
+
19
+ # Insert the Moonrope middleware into the application's middleware
20
+ # stack (at the bottom).
21
+ app.middleware.use(
22
+ Moonrope::RackMiddleware,
23
+ app.config.moonrope,
24
+ :reload_on_each_request => !app.config.cache_classes
25
+ )
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,165 @@
1
+ module Moonrope
2
+ class Request
3
+
4
+ # The path which will be intercepted by the Rack middleware
5
+ # and that all reuqest will arrive on.
6
+ PATH_REGEX = /\A\/api\/([\w\/\-\.]+)?/
7
+
8
+ # @return [Hash] the rack environment
9
+ attr_reader :env
10
+ # @return [String] the name of the controller which was requested
11
+ attr_reader :controller_name
12
+ # @return [String] the name of the action which was requested
13
+ attr_reader :action_name
14
+ # @return [Object] the authenticated user
15
+ attr_reader :authenticated_user
16
+
17
+ #
18
+ # Initialize a new Moonrope::Request
19
+ #
20
+ # @param base [Moonrope::Base]
21
+ # @param env [Hash] a rack environment has
22
+ # @param path [String] the reqested path (after the /api/ prefix)
23
+ #
24
+ def initialize(base, env, path = nil)
25
+ @base = base
26
+ @env = env
27
+ if path.nil? && env['PATH_INFO'] =~ PATH_REGEX
28
+ path = $1
29
+ end
30
+ @version, @controller_name, @action_name = path ? path.split("/") : [nil, nil, nil]
31
+ end
32
+
33
+ #
34
+ # Return the requested API version from the request
35
+ #
36
+ # @return [Integer]
37
+ #
38
+ def version
39
+ version = @version.to_s.gsub(/[^0-9]/, '').to_i
40
+ version = 1 if version == 0
41
+ version
42
+ end
43
+
44
+ #
45
+ # Return whether or not this request is valid and can continue?
46
+ #
47
+ # @return [Boolean]
48
+ #
49
+ def valid?
50
+ !!(version > 0 && [controller_name, action_name].all? { |c| c =~ /\A[\w\-\.]+\z/} && controller && action)
51
+ end
52
+
53
+ #
54
+ # Return the controller object for the request
55
+ #
56
+ # @return [Moonrope::Controller]
57
+ #
58
+ def controller
59
+ @controller ||= @base.controller(controller_name.to_sym)
60
+ end
61
+
62
+ #
63
+ # Return the action object for the request
64
+ #
65
+ # return [Moonrope::Action]
66
+ #
67
+ def action
68
+ @action ||= controller.actions[action_name.to_sym]
69
+ end
70
+
71
+ #
72
+ # Execute the appropriate action for the request after running
73
+ # the various authentication checks.
74
+ #
75
+ # @return [Moonrope::ActionResult]
76
+ #
77
+ def execute
78
+ eval_env = EvalEnvironment.new(@base, self)
79
+ if @base.authenticator
80
+ begin
81
+ @authenticated_user = eval_env.instance_eval(&@base.authenticator)
82
+ # If we are authenticated, check whether the action permits access to
83
+ # this user, if not raise an error.
84
+ if authenticated?
85
+ unless action.check_access(eval_env) == true
86
+ raise Moonrope::Errors::AccessDenied, "Access to #{controller.name}/#{action.name} is not permitted."
87
+ end
88
+ end
89
+ rescue Moonrope::Errors::RequestError => e
90
+ @authenticated_user ||= false # set authenticated user to false if they don't exist
91
+ result = Moonrope::ActionResult.new(self)
92
+ result.status = e.status
93
+ result.data = e.data
94
+ return result
95
+ end
96
+ end
97
+ action.execute(eval_env)
98
+ end
99
+
100
+ #
101
+ # Return all user supplier parameters
102
+ #
103
+ # @return [Moonrope::ParamSet]
104
+ #
105
+ def params
106
+ @params ||= Moonrope::ParamSet.new(rack_request.params['params'])
107
+ end
108
+
109
+ #
110
+ # Return all HTTP headers from the request
111
+ #
112
+ # @return [Rack::Utils::HeaderHash]
113
+ #
114
+ def headers
115
+ @headers ||= self.class.extract_http_request_headers(@env)
116
+ end
117
+
118
+ #
119
+ # Is this request to the API anonymous?
120
+ #
121
+ # @return [Boolean]
122
+ #
123
+ def anonymous?
124
+ authenticated_user.nil?
125
+ end
126
+
127
+ #
128
+ # Is this request to the API authenticated?
129
+ #
130
+ # @return [Boolean]
131
+ #
132
+ def authenticated?
133
+ !(authenticated_user.nil? || authenticated_user == false)
134
+ end
135
+
136
+ private
137
+
138
+ #
139
+ # Return/create a rack request object for use internally
140
+ #
141
+ # @return [Rack::Request]
142
+ #
143
+ def rack_request
144
+ @rack_request ||= ::Rack::Request.new(@env)
145
+ end
146
+
147
+ #
148
+ # Extract headers from the rack env
149
+ #
150
+ # @return [Rack::Utils::HeaderHash]
151
+ #
152
+ def self.extract_http_request_headers(env)
153
+ env.reject do |k, v|
154
+ !(/^HTTP_[A-Z_]+$/ === k) || v.nil?
155
+ end.map do |k, v|
156
+ [k.sub(/^HTTP_/, "").gsub("_", "-"), v]
157
+ end.inject(::Rack::Utils::HeaderHash.new) do |hash, k_v|
158
+ k, v = k_v
159
+ hash[k] = v
160
+ hash
161
+ end
162
+ end
163
+
164
+ end
165
+ end
@@ -0,0 +1,83 @@
1
+ module Moonrope
2
+ class Structure
3
+
4
+ # @return [Symbol] the name of the structure
5
+ attr_accessor :name
6
+
7
+ # @return [Proc] the basic data block
8
+ attr_accessor :basic
9
+
10
+ # @return [Proc] the full data block
11
+ attr_accessor :full
12
+
13
+ # @return [Moonrope::DSL::StructureDSL] the DSL
14
+ attr_reader :dsl
15
+
16
+ # @return [Hash] all expansions for the structure
17
+ attr_reader :expansions
18
+
19
+ # @return [Array] all restrictions for the structure
20
+ attr_reader :restrictions
21
+
22
+ # @return [Moonrope::Base] the base API
23
+ attr_reader :base
24
+
25
+ #
26
+ # Initialize a new structure
27
+ #
28
+ # @param base [Moonrope::Base]
29
+ # @param name [Symbol]
30
+ # @yield instance evals the contents within the structure DSL
31
+ def initialize(base, name, &block)
32
+ @base = base
33
+ @name = name
34
+ @expansions = {}
35
+ @restrictions = []
36
+ @dsl = Moonrope::DSL::StructureDSL.new(self)
37
+ @dsl.instance_eval(&block) if block_given?
38
+ end
39
+
40
+ #
41
+ # Return a hash for this struture
42
+ #
43
+ # @param object [Object] the object
44
+ # @param options [Hash] additional options
45
+ # @return [Hash]
46
+ #
47
+ def hash(object, options = {})
48
+ # Set up an environment for
49
+ environment = EvalEnvironment.new(base, options[:request], :o => object)
50
+
51
+ # Always get a basic hash to work from
52
+ hash = environment.instance_eval(&self.basic)
53
+
54
+ # Enhance with the full hash if requested
55
+ if options[:full]
56
+ if self.full.is_a?(Proc)
57
+ full_hash = environment.instance_eval(&self.full)
58
+ hash.merge!(full_hash)
59
+ end
60
+
61
+ # Add restrictions
62
+ if environment.auth
63
+ @restrictions.each do |restriction|
64
+ next unless environment.instance_eval(&restriction.condition) == true
65
+ hash.merge!(environment.instance_eval(&restriction.data))
66
+ end
67
+ end
68
+ end
69
+
70
+ # Add expansions
71
+ if options[:expansions]
72
+ expansions.each do |name, expansion|
73
+ next if options[:expansions].is_a?(Array) && !options[:expansions].include?(name.to_sym)
74
+ hash.merge!(name => environment.instance_eval(&expansion))
75
+ end
76
+ end
77
+
78
+ # Return the hash
79
+ hash
80
+ end
81
+
82
+ end
83
+ end