foundationapi 0.9.9

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.
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