foundationapi 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/Rakefile +37 -0
  2. data/lib/foundation_api.rb +9 -0
  3. data/lib/foundation_api/errors.rb +34 -0
  4. data/lib/foundation_api/event.rb +6 -0
  5. data/lib/foundation_api/event/client.rb +105 -0
  6. data/lib/foundation_api/event/exceptions.rb +20 -0
  7. data/lib/foundation_api/json_rpc.rb +6 -0
  8. data/lib/foundation_api/json_rpc/client.rb +80 -0
  9. data/lib/foundation_api/json_rpc/exceptions.rb +47 -0
  10. data/lib/foundation_api/model.rb +7 -0
  11. data/lib/foundation_api/model/attribute_methods.rb +85 -0
  12. data/lib/foundation_api/model/cached.rb +13 -0
  13. data/lib/foundation_api/model/mapping.rb +24 -0
  14. data/lib/foundation_api/request.rb +29 -0
  15. data/lib/foundation_api/service.rb +56 -0
  16. data/lib/foundation_api/shoulda_matcher.rb +15 -0
  17. data/lib/foundation_api/shoulda_matcher/attribute_alias_matcher.rb +32 -0
  18. data/lib/foundation_api/shoulda_matcher/persistence_method_matcher.rb +91 -0
  19. data/lib/foundation_api/table.rb +6 -0
  20. data/lib/foundation_api/table/persistence.rb +115 -0
  21. data/lib/foundation_api/table/record.rb +143 -0
  22. data/lib/foundation_api/test_helper.rb +53 -0
  23. data/lib/foundation_api/version.rb +27 -0
  24. data/test/foundation_api_test.rb +31 -0
  25. data/test/test_helper.rb +31 -0
  26. data/test/unit/foundation_api/event/client_test.rb +129 -0
  27. data/test/unit/foundation_api/json_rpc/client_test.rb +143 -0
  28. data/test/unit/foundation_api/model/attribute_methods_test.rb +96 -0
  29. data/test/unit/foundation_api/model/cached_test.rb +20 -0
  30. data/test/unit/foundation_api/model/mapping_test.rb +22 -0
  31. data/test/unit/foundation_api/request_test.rb +33 -0
  32. data/test/unit/foundation_api/service_test.rb +54 -0
  33. data/test/unit/foundation_api/table/persistence_test.rb +182 -0
  34. data/test/unit/foundation_api/table/record_test.rb +176 -0
  35. metadata +209 -0
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'FoundationApi'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ Dir.glob('lib/tasks/*.rake').each { |r| import r }
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task :default => :test
@@ -0,0 +1,9 @@
1
+ require 'foundation_api/errors'
2
+ module FoundationApi
3
+ autoload :JsonRPC, 'foundation_api/json_rpc'
4
+ autoload :Event, 'foundation_api/event'
5
+ autoload :Model, 'foundation_api/model'
6
+ autoload :Request, 'foundation_api/request'
7
+ autoload :Table, 'foundation_api/table'
8
+ autoload :Service, 'foundation_api/service'
9
+ end
@@ -0,0 +1,34 @@
1
+ module FoundationApi
2
+ class FoundationApiError < StandardError
3
+ end
4
+
5
+ class RecordNotUnique < FoundationApiError
6
+ end
7
+
8
+ class RecordNotSaved < FoundationApiError
9
+ end
10
+
11
+ class RecordNotDeleted < FoundationApiError
12
+ end
13
+
14
+ class RecordNotPersistent < FoundationApiError
15
+ end
16
+
17
+ class TranslationError < FoundationApiError
18
+ end
19
+
20
+ class PersistenceNotSupported < FoundationApiError
21
+ end
22
+
23
+ class CreateNotSupported < PersistenceNotSupported
24
+ end
25
+
26
+ class UpdateNotSupported < PersistenceNotSupported
27
+ end
28
+
29
+ class DestroyNotSupported < PersistenceNotSupported
30
+ end
31
+
32
+ class RecordInvalid < FoundationApiError
33
+ end
34
+ end
@@ -0,0 +1,6 @@
1
+ module FoundationApi
2
+ module Event
3
+ autoload :Exceptions, 'foundation_api/event/exceptions'
4
+ autoload :Client, 'foundation_api/event/client'
5
+ end
6
+ end
@@ -0,0 +1,105 @@
1
+ require 'net/http'
2
+ require 'addressable/uri'
3
+ require 'json'
4
+ require 'active_support/benchmarkable'
5
+
6
+ require File.expand_path(File.join(File.dirname(__FILE__), 'exceptions'))
7
+
8
+ module FoundationApi
9
+ module Event
10
+ class Client
11
+ class_attribute :uri
12
+ class_attribute :auth_token
13
+ class << self
14
+ include ActiveSupport::Benchmarkable
15
+
16
+ def site=(url)
17
+ self.uri = Addressable::URI.parse(url)
18
+ end
19
+
20
+ def logger
21
+ ::Rails.logger
22
+ end
23
+
24
+ def authenticate
25
+ logger.debug "FoundationApi::Event::Client authenticating"
26
+ self.auth_token = FoundationApi::JsonRPC::Client.authenticate
27
+ logger.debug "auth_token: #{auth_token.inspect}"
28
+ self.auth_token
29
+ end
30
+
31
+ def request(interface, params = {})
32
+ interface = interface.to_s
33
+ tries = 0
34
+ begin
35
+ authenticate unless auth_token
36
+ post_request interface, params.merge(:apitoken => auth_token)
37
+ rescue => e
38
+ if (tries += 1) == 1
39
+ case e
40
+ when AuthenticationFailed
41
+ self.auth_token = nil
42
+ retry
43
+ when FoundationApi::JsonRPC::JsonRPCError
44
+ retry if e.response == :invalid_security_token
45
+ end
46
+ end
47
+ raise
48
+ end
49
+ end
50
+
51
+ # call to send an unauthenticated request
52
+ def post_request(interface, params = {})
53
+ result = {}
54
+ logger.info "Initiating FoundationApi::Event::Client POST #{uri.join(interface)} at #{Time.now}"
55
+ benchmark "Connection time", :level => :info do
56
+ connect do |connection|
57
+ message = params.to_json
58
+ logger.info " Parameters: #{password_filter(message)}"
59
+ benchmark " Request time", :level => :info do
60
+ result = JSON.parse(connection.post(uri.join(interface).path, message, {'Content-Type' => 'application/json', 'Accept' => '*/*'}).body)
61
+ end
62
+ end
63
+ handle_response result, interface
64
+ logger.info " FoundationApi::Event::Client.post_request result: #{result.inspect}"
65
+ end
66
+ result['status']
67
+ end
68
+
69
+ protected
70
+ def connect(&block)
71
+ conn = http
72
+ logger.debug " Using: #{uri.scheme}"
73
+ if uri.scheme == "https"
74
+ conn.use_ssl = true
75
+ conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
76
+ end
77
+ conn.start(&block)
78
+ end
79
+
80
+ def http
81
+ Net::HTTP.new(uri.host, uri.port)
82
+ end
83
+
84
+ private
85
+ def password_filter(str)
86
+ str.gsub(/"password":\s*".*?"/, '"password":"[FILTERED]"')
87
+ end
88
+
89
+ def handle_response(result, interface)
90
+ unless result['status'] == "SUCCESS"
91
+ logger.error " FoundationApi::Event::RequestError: #{interface} -> #{result.inspect}"
92
+ begin
93
+ e = "FoundationApi::Event::#{result['status'].downcase.classify}".constantize
94
+ rescue
95
+ logger.error "Unknown result from #{uri.join(interface)}"
96
+ raise FoundationApi::Event::UnknownResponseError, "Unknow response: #{result.inspect}"
97
+ end
98
+ raise e, "FoundationApi::Event::Client.post_request failed #{uri.join(interface)} returned #{result.inspect}"
99
+ end
100
+ end
101
+ end
102
+
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,20 @@
1
+ module FoundationApi
2
+ module Event
3
+
4
+ class RequestError < StandardError
5
+ end
6
+
7
+ class AuthenticationFailed < RequestError
8
+ end
9
+ class InvalidArgument < RequestError
10
+ end
11
+ class MalformedRequest < RequestError
12
+ end
13
+ class OperationError < RequestError
14
+ end
15
+ class Timeout < RequestError
16
+ end
17
+ class UnknownResponseError < RequestError
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ module FoundationApi
2
+ module JsonRPC
3
+ autoload :JsonRPCError, 'foundation_api/json_rpc/exceptions'
4
+ autoload :Client, 'foundation_api/json_rpc/client'
5
+ end
6
+ end
@@ -0,0 +1,80 @@
1
+ require 'net/http'
2
+ require 'addressable/uri'
3
+ require 'json'
4
+
5
+ require File.expand_path(File.join(File.dirname(__FILE__), 'exceptions'))
6
+
7
+ module FoundationApi
8
+ module JsonRPC
9
+ class Client
10
+
11
+ class_attribute :uri
12
+ class_attribute :auth_token
13
+
14
+ class << self
15
+ include ActiveSupport::Benchmarkable
16
+
17
+ def site=(url)
18
+ self.uri = Addressable::URI.parse(url)
19
+ end
20
+
21
+ def logger
22
+ ::Rails.logger
23
+ end
24
+
25
+ def authenticate
26
+ self.auth_token = nil
27
+ self.auth_token = post_request('APIAuthenticate', :username => uri.user, :password => uri.password)
28
+ end
29
+
30
+ def request(method, params = {})
31
+ tries = 0
32
+ begin
33
+ authenticate unless auth_token
34
+ post_request method, params.merge(:authorization => auth_token)
35
+ rescue JsonRPCError => e
36
+ tries += 1
37
+ if e.response == :invalid_security_token && tries == 1
38
+ self.auth_token = nil
39
+ retry
40
+ end
41
+ raise
42
+ end
43
+ end
44
+
45
+ # call to send an unauthenticated request
46
+ def post_request(method, params = {})
47
+ result = {}
48
+ Net::HTTP.start(uri.host, uri.port) do |connection|
49
+ logger.debug "Started JsonRPC POST #{uri.path} at #{Time.now}"
50
+ message = {:method => method.to_s, :id => 'jsonrpc', :params => [params]}.to_json
51
+ logger.debug "Parameters: #{password_filter(message)}"
52
+ benchmark "Request time", :level => :info do
53
+ result = JSON.parse(connection.post(uri.path, message).body)
54
+ end
55
+ end
56
+ if error = result["error"]
57
+ logger.error "FoundationApi::JsonRPC::JsonRPCError: #{method} -> #{result.inspect}"
58
+ raise JsonRPCError.new(error['code']), error["message"]
59
+ else
60
+ logger.debug "FoundationApi::JsonRPC::Client.post_request result: #{result.inspect}"
61
+ end
62
+ result['result']
63
+ end
64
+
65
+ def unique_id(seed)
66
+ "#{Time.now.to_f}.#{seed}"
67
+ end
68
+
69
+ def key_value_array(attributes = {})
70
+ attributes.stringify_keys.sort
71
+ end
72
+ private
73
+ def password_filter(str)
74
+ str.gsub(/\["password",".*?"\]/, '["password","[FILTERED]"]')
75
+ end
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,47 @@
1
+ module FoundationApi
2
+ module JsonRPC
3
+
4
+ class JsonRPCError < StandardError
5
+ RESPONSES =
6
+ [:no_error ,
7
+ :invalid_security_token ,
8
+ :invalid_user_or_pass ,
9
+ :exception_thrown ,
10
+ :not_implemented ,
11
+ :incorrect_parameter_list ,
12
+ :missing_player_session ,
13
+ :unable_to_get_session_lock ,
14
+ :session_server_fault ,
15
+ :insufficient_privileges ,
16
+ :incorrect_account_format ,
17
+ :invalid_parameter , # 11
18
+ :unknown_player_attribute , # 12
19
+ :cannot_set_readonly_attribute , # 13
20
+ :duplicate_attribute_value , # 14
21
+ :unknown_player_flag , # 15
22
+ :cannot_set_readonly_flag , # 16
23
+ :nothing_to_update , # 17
24
+ :unknown_error , # 18
25
+ :mismatching_transactions , # 19
26
+ :db_error , # 20
27
+ :un_identified_error_21 , # 21
28
+ :un_identified_error_22 , # 22
29
+ :un_identified_error_23 , # 23
30
+ :insufficient_funds
31
+ ]
32
+
33
+ attr_reader :code
34
+ def initialize(code = nil)
35
+ @code = code.to_i if code
36
+ end
37
+
38
+ def response
39
+ RESPONSES[code]
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+
46
+
47
+ end
@@ -0,0 +1,7 @@
1
+ module FoundationApi
2
+ module Model
3
+ autoload :AttributeMethods, 'foundation_api/model/attribute_methods'
4
+ autoload :Cached, 'foundation_api/model/cached'
5
+ autoload :Mapping, 'foundation_api/model/mapping'
6
+ end
7
+ end
@@ -0,0 +1,85 @@
1
+ require 'active_support/hash_with_indifferent_access.rb'
2
+ module FoundationApi
3
+ module Model
4
+ module AttributeMethods
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :attribute_aliases
9
+ self.attribute_aliases = {}
10
+ attr_accessor :attributes
11
+ end
12
+
13
+ module ClassMethods
14
+ def alias_attribute(new_name, old_name)
15
+ self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
16
+ end
17
+
18
+ def map_attribute(attr)
19
+ attribute_aliases[attr.to_s] || attr.to_s
20
+ end
21
+ def reverse_map_attribute(attr)
22
+ if v = attribute_aliases.rassoc(attr.to_s)
23
+ v[0].to_s
24
+ else
25
+ attr.to_s
26
+ end
27
+ end
28
+ def translate_attributes(attrs)
29
+ HashWithIndifferentAccess[attrs.collect { |attr,value| [map_attribute(attr), value] }]
30
+ end
31
+ end
32
+
33
+ # TODO: change when on rails 4
34
+ # delegate :map_attribute, :reverse_map_attribute, to: :class
35
+ delegate :map_attribute, :reverse_map_attribute, :translate_attributes, to: 'self.class'
36
+
37
+ def method_missing(method, *args)
38
+ if method =~ /([_a-zA-Z]\w*)([=?]*)/
39
+ name, call = $1, $2
40
+ if attribute = matching_attribute(name)
41
+ case call
42
+ when "="
43
+ src = <<-end_src
44
+ def #{name}=(value)
45
+ attributes[:#{attribute}] = value
46
+ end
47
+ end_src
48
+ class_eval src, __FILE__, __LINE__
49
+ when "?"
50
+ src = <<-end_src
51
+ def #{name}?
52
+ !!attributes[:#{attribute}]
53
+ end
54
+ end_src
55
+ class_eval src, __FILE__, __LINE__
56
+ else
57
+ src = <<-end_src
58
+ def #{name}
59
+ attributes[:#{attribute}]
60
+ end
61
+ end_src
62
+ class_eval src, __FILE__, __LINE__
63
+ end
64
+ return self.send(method, *args)
65
+ end
66
+ end
67
+ super
68
+ end
69
+
70
+ def [](attribute)
71
+ attributes[map_attribute(attribute)]
72
+ end
73
+
74
+ def []=(attribute, value)
75
+ attributes[map_attribute(attribute)] = value
76
+ end
77
+
78
+ private
79
+ def matching_attribute(attr)
80
+ attributes.has_key?( attr) ? attr : attribute_aliases[attr.to_s]
81
+ end
82
+
83
+ end
84
+ end
85
+ end