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.
- data/Rakefile +37 -0
- data/lib/foundation_api.rb +9 -0
- data/lib/foundation_api/errors.rb +34 -0
- data/lib/foundation_api/event.rb +6 -0
- data/lib/foundation_api/event/client.rb +105 -0
- data/lib/foundation_api/event/exceptions.rb +20 -0
- data/lib/foundation_api/json_rpc.rb +6 -0
- data/lib/foundation_api/json_rpc/client.rb +80 -0
- data/lib/foundation_api/json_rpc/exceptions.rb +47 -0
- data/lib/foundation_api/model.rb +7 -0
- data/lib/foundation_api/model/attribute_methods.rb +85 -0
- data/lib/foundation_api/model/cached.rb +13 -0
- data/lib/foundation_api/model/mapping.rb +24 -0
- data/lib/foundation_api/request.rb +29 -0
- data/lib/foundation_api/service.rb +56 -0
- data/lib/foundation_api/shoulda_matcher.rb +15 -0
- data/lib/foundation_api/shoulda_matcher/attribute_alias_matcher.rb +32 -0
- data/lib/foundation_api/shoulda_matcher/persistence_method_matcher.rb +91 -0
- data/lib/foundation_api/table.rb +6 -0
- data/lib/foundation_api/table/persistence.rb +115 -0
- data/lib/foundation_api/table/record.rb +143 -0
- data/lib/foundation_api/test_helper.rb +53 -0
- data/lib/foundation_api/version.rb +27 -0
- data/test/foundation_api_test.rb +31 -0
- data/test/test_helper.rb +31 -0
- data/test/unit/foundation_api/event/client_test.rb +129 -0
- data/test/unit/foundation_api/json_rpc/client_test.rb +143 -0
- data/test/unit/foundation_api/model/attribute_methods_test.rb +96 -0
- data/test/unit/foundation_api/model/cached_test.rb +20 -0
- data/test/unit/foundation_api/model/mapping_test.rb +22 -0
- data/test/unit/foundation_api/request_test.rb +33 -0
- data/test/unit/foundation_api/service_test.rb +54 -0
- data/test/unit/foundation_api/table/persistence_test.rb +182 -0
- data/test/unit/foundation_api/table/record_test.rb +176 -0
- metadata +209 -0
data/Rakefile
ADDED
@@ -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,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,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,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
|