moonrope 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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