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