api_client 0.5.24-java → 0.5.25-java
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/api_client/base.rb +77 -0
- data/lib/api_client/connection/abstract.rb +81 -0
- data/lib/api_client/connection/basic.rb +129 -0
- data/lib/api_client/connection/json.rb +14 -0
- data/lib/api_client/connection/middlewares/request/json.rb +34 -0
- data/lib/api_client/connection/middlewares/request/logger.rb +64 -0
- data/lib/api_client/connection/middlewares/request/oauth.rb +22 -0
- data/lib/api_client/connection/oauth.rb +18 -0
- data/lib/api_client/errors.rb +31 -0
- data/lib/api_client/mixins/configuration.rb +24 -0
- data/lib/api_client/mixins/connection_hooks.rb +24 -0
- data/lib/api_client/mixins/delegation.rb +23 -0
- data/lib/api_client/mixins/inheritance.rb +19 -0
- data/lib/api_client/mixins/instantiation.rb +29 -0
- data/lib/api_client/mixins/scoping.rb +49 -0
- data/lib/api_client/resource/base.rb +67 -0
- data/lib/api_client/resource/name_resolver.rb +37 -0
- data/lib/api_client/resource/scope.rb +73 -0
- data/lib/api_client/scope.rb +125 -0
- data/lib/api_client/utils.rb +18 -0
- data/lib/api_client/version.rb +3 -0
- data/spec/api_client/base/connection_hook_spec.rb +18 -0
- data/spec/api_client/base/delegation_spec.rb +15 -0
- data/spec/api_client/base/inheritance_spec.rb +44 -0
- data/spec/api_client/base/instantiation_spec.rb +55 -0
- data/spec/api_client/base/marshalling_spec.rb +33 -0
- data/spec/api_client/base/parsing_spec.rb +38 -0
- data/spec/api_client/base/scoping_spec.rb +60 -0
- data/spec/api_client/base_spec.rb +107 -0
- data/spec/api_client/connection/abstract_spec.rb +21 -0
- data/spec/api_client/connection/basic_spec.rb +191 -0
- data/spec/api_client/connection/oauth_spec.rb +27 -0
- data/spec/api_client/connection/request/json_spec.rb +30 -0
- data/spec/api_client/connection/request/logger_spec.rb +18 -0
- data/spec/api_client/connection/request/oauth_spec.rb +26 -0
- data/spec/api_client/resource/base_spec.rb +97 -0
- data/spec/api_client/resource/name_spec.rb +19 -0
- data/spec/api_client/resource/scope_spec.rb +122 -0
- data/spec/api_client/scope_spec.rb +204 -0
- data/spec/api_client/utils_spec.rb +32 -0
- data/spec/support/matchers.rb +5 -0
- metadata +62 -1
@@ -0,0 +1,18 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
module Connection
|
4
|
+
|
5
|
+
class Oauth < Basic
|
6
|
+
|
7
|
+
def finalize_handler
|
8
|
+
@handler.use Middlewares::Request::Logger, ApiClient.logger if ApiClient.logger
|
9
|
+
@handler.use Middlewares::Request::OAuth, @options[:oauth]
|
10
|
+
@handler.use Faraday::Request::UrlEncoded
|
11
|
+
@handler.adapter Faraday.default_adapter
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
module Errors
|
4
|
+
class ApiClientError < StandardError
|
5
|
+
def initialize(message = nil, request = nil, response = nil)
|
6
|
+
message ||= "Status code: #{response.status}" if response
|
7
|
+
super(message)
|
8
|
+
@request = request
|
9
|
+
@response = response
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :request, :response
|
13
|
+
end
|
14
|
+
|
15
|
+
class ConnectionFailed < ApiClientError; end
|
16
|
+
class Config < ApiClientError; end
|
17
|
+
class Unauthorized < ApiClientError; end
|
18
|
+
class Forbidden < ApiClientError; end
|
19
|
+
class NotFound < ApiClientError; end
|
20
|
+
class Redirect < ApiClientError; end
|
21
|
+
class BadRequest < ApiClientError; end
|
22
|
+
class Unsupported < ApiClientError; end
|
23
|
+
class Conflict < ApiClientError; end
|
24
|
+
class Gone < ApiClientError; end
|
25
|
+
class ServerError < ApiClientError; end
|
26
|
+
class UnprocessableEntity < ApiClientError; end
|
27
|
+
class Locked < ApiClientError; end
|
28
|
+
class TooManyRequests < ApiClientError; end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
module Mixins
|
4
|
+
|
5
|
+
module Configuration
|
6
|
+
|
7
|
+
def dsl_accessor(*names)
|
8
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
9
|
+
names.each do |name|
|
10
|
+
returns = options[:return_self] ? "self" : "@#{name}"
|
11
|
+
class_eval <<-STR
|
12
|
+
def #{name}(value = nil)
|
13
|
+
value.nil? ? @#{name} : @#{name} = value
|
14
|
+
#{returns}
|
15
|
+
end
|
16
|
+
STR
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
module Mixins
|
4
|
+
|
5
|
+
module ConnectionHooks
|
6
|
+
|
7
|
+
attr_accessor :connection_hooks
|
8
|
+
|
9
|
+
def connection(&block)
|
10
|
+
@connection_hooks ||= []
|
11
|
+
@connection_hooks.push(block) if block
|
12
|
+
@connection_hooks
|
13
|
+
end
|
14
|
+
|
15
|
+
def connection_hooks
|
16
|
+
@connection_hooks || []
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
module Mixins
|
4
|
+
|
5
|
+
module Delegation
|
6
|
+
|
7
|
+
def delegate(*methods)
|
8
|
+
hash = methods.pop
|
9
|
+
to = hash[:to]
|
10
|
+
methods.each do |method|
|
11
|
+
class_eval <<-STR
|
12
|
+
def #{method}(*args, &block)
|
13
|
+
#{to}.#{method}(*args, &block)
|
14
|
+
end
|
15
|
+
STR
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
module Mixins
|
4
|
+
|
5
|
+
module Inheritance
|
6
|
+
|
7
|
+
def inherited(subclass)
|
8
|
+
subclass.default_scopes = self.default_scopes.dup
|
9
|
+
subclass.connection_hooks = self.connection_hooks.dup
|
10
|
+
|
11
|
+
subclass.namespace self.namespace
|
12
|
+
subclass.format self.format
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ApiClient
|
2
|
+
module Mixins
|
3
|
+
module Instantiation
|
4
|
+
def self.extended(base)
|
5
|
+
base.instance_eval do
|
6
|
+
attr_accessor :original_scope
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def build_one(hash)
|
11
|
+
instance = self.new self.namespace ? hash[namespace] : hash
|
12
|
+
instance.original_scope = self.scope.clone_only_headers
|
13
|
+
instance
|
14
|
+
end
|
15
|
+
|
16
|
+
def build_many(array)
|
17
|
+
array.collect { |one| build_one(one) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def build(result_or_array)
|
21
|
+
if result_or_array.is_a?(Array)
|
22
|
+
build_many result_or_array
|
23
|
+
else
|
24
|
+
build_one result_or_array
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
module Mixins
|
4
|
+
|
5
|
+
module Scoping
|
6
|
+
|
7
|
+
attr_accessor :default_scopes
|
8
|
+
|
9
|
+
# Default scoping
|
10
|
+
def always(&block)
|
11
|
+
default_scopes.push(block) if block
|
12
|
+
end
|
13
|
+
|
14
|
+
def default_scopes
|
15
|
+
@default_scopes || []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Scoping
|
19
|
+
def scope(options = {})
|
20
|
+
scope_in_thread || Scope.new(self).params(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Allow wrapping singleton methods in a scope
|
24
|
+
# Store the handler in a thread-local variable for thread safety
|
25
|
+
def scoped(scope)
|
26
|
+
Thread.current[scope_thread_attribute_name] ||= []
|
27
|
+
Thread.current[scope_thread_attribute_name].push scope
|
28
|
+
begin
|
29
|
+
yield
|
30
|
+
ensure
|
31
|
+
Thread.current[scope_thread_attribute_name] = nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def scope_thread_attribute_name
|
36
|
+
"#{self.name}_scope"
|
37
|
+
end
|
38
|
+
|
39
|
+
def scope_in_thread
|
40
|
+
if found = Thread.current[scope_thread_attribute_name]
|
41
|
+
found.last
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
module Resource
|
4
|
+
|
5
|
+
class Base < ApiClient::Base
|
6
|
+
|
7
|
+
class << self
|
8
|
+
extend ApiClient::Mixins::Delegation
|
9
|
+
extend ApiClient::Mixins::Configuration
|
10
|
+
|
11
|
+
delegate :find_all, :find, :create, :update, :destroy, :path, :to => :scope
|
12
|
+
|
13
|
+
dsl_accessor :prefix
|
14
|
+
|
15
|
+
def inherited(subclass)
|
16
|
+
super
|
17
|
+
small_name = NameResolver.resolve(subclass.name)
|
18
|
+
subclass.namespace small_name
|
19
|
+
subclass.prefix self.prefix
|
20
|
+
subclass.always do
|
21
|
+
name = small_name
|
22
|
+
pre_fix = prefix
|
23
|
+
path ["", prefix, "#{name}s"].compact.join('/')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def scope(options = {})
|
28
|
+
scope_in_thread || ApiClient::Resource::Scope.new(self).params(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def persisted?
|
34
|
+
!!self.id
|
35
|
+
end
|
36
|
+
|
37
|
+
def save
|
38
|
+
self.persisted? ? remote_update : remote_create
|
39
|
+
end
|
40
|
+
|
41
|
+
def destroy
|
42
|
+
get_scope.destroy(self.id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def payload
|
46
|
+
hash = self.to_hash
|
47
|
+
hash.delete('id') # This key is never required
|
48
|
+
hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def remote_update
|
52
|
+
get_scope.update(self.id, payload)
|
53
|
+
end
|
54
|
+
|
55
|
+
def remote_create
|
56
|
+
get_scope.create(payload)
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_scope
|
60
|
+
original_scope || self.class
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ApiClient
|
2
|
+
module Resource
|
3
|
+
class NameResolver
|
4
|
+
def self.resolve(ruby_path)
|
5
|
+
new(ruby_path).resolve
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
def initialize(name)
|
11
|
+
@name = name
|
12
|
+
end
|
13
|
+
|
14
|
+
def resolve
|
15
|
+
select_last_item
|
16
|
+
underscorize
|
17
|
+
lowercase
|
18
|
+
name
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def select_last_item
|
23
|
+
@name = @name.split('::').last
|
24
|
+
end
|
25
|
+
|
26
|
+
#Inspired by ActiveSupport::Inflector#underscore
|
27
|
+
def underscorize
|
28
|
+
@name.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
29
|
+
@name.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
30
|
+
end
|
31
|
+
|
32
|
+
def lowercase
|
33
|
+
@name.downcase!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# This class includes methods for calling restful APIs
|
2
|
+
module ApiClient
|
3
|
+
|
4
|
+
module Resource
|
5
|
+
|
6
|
+
class Scope < ApiClient::Scope
|
7
|
+
|
8
|
+
dsl_accessor :path, :return_self => true
|
9
|
+
|
10
|
+
def format
|
11
|
+
@scopeable.format
|
12
|
+
end
|
13
|
+
|
14
|
+
def append_format(path)
|
15
|
+
format ? [path, format].join('.') : path
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(id)
|
19
|
+
path = [@path, id].join('/')
|
20
|
+
path = append_format(path)
|
21
|
+
response = get(path)
|
22
|
+
scoped(self) do
|
23
|
+
raw? ? response : @scopeable.build(response)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_all(params = {})
|
28
|
+
path = append_format(@path)
|
29
|
+
response = get(path, params)
|
30
|
+
scoped(self) do
|
31
|
+
raw? ? response : @scopeable.build(response)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def create(params = {})
|
36
|
+
path = append_format(@path)
|
37
|
+
hash = if @scopeable.namespace
|
38
|
+
{ @scopeable.namespace => params }
|
39
|
+
else
|
40
|
+
params
|
41
|
+
end
|
42
|
+
response = post(path, hash)
|
43
|
+
scoped(self) do
|
44
|
+
raw? ? response : @scopeable.build(response)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def update(id, params = {})
|
49
|
+
path = [@path, id].join('/')
|
50
|
+
path = append_format(path)
|
51
|
+
hash = if @scopeable.namespace
|
52
|
+
{ @scopeable.namespace => params }
|
53
|
+
else
|
54
|
+
params
|
55
|
+
end
|
56
|
+
response = put(path, hash)
|
57
|
+
scoped(self) do
|
58
|
+
raw? ? response : @scopeable.build(response)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def destroy(id)
|
63
|
+
path = [@path, id].join('/')
|
64
|
+
path = append_format(path)
|
65
|
+
delete(path)
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module ApiClient
|
2
|
+
|
3
|
+
class Scope
|
4
|
+
extend ApiClient::Mixins::Configuration
|
5
|
+
extend ApiClient::Mixins::Delegation
|
6
|
+
|
7
|
+
delegate :prefix, :scoped, :to => :scopeable
|
8
|
+
|
9
|
+
dsl_accessor :endpoint, :adapter, :return_self => true
|
10
|
+
|
11
|
+
attr_reader :scopeable
|
12
|
+
|
13
|
+
def initialize(scopeable)
|
14
|
+
@scopeable = scopeable
|
15
|
+
@params = {}
|
16
|
+
@headers = {}
|
17
|
+
@options = {}
|
18
|
+
@scopeable.default_scopes.each do |default_scope|
|
19
|
+
self.instance_eval(&default_scope)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection
|
24
|
+
klass = Connection.const_get((@adapter || Connection.default).to_s.capitalize)
|
25
|
+
@connection = klass.new(@endpoint , @options || {})
|
26
|
+
hooks = @scopeable.connection_hooks || []
|
27
|
+
hooks.each { |hook| hook.call(@connection, self) }
|
28
|
+
@connection
|
29
|
+
end
|
30
|
+
|
31
|
+
def raw
|
32
|
+
@raw = true
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def raw?
|
37
|
+
!!@raw
|
38
|
+
end
|
39
|
+
|
40
|
+
# 3 Pillars of scoping
|
41
|
+
# options - passed on the the adapter
|
42
|
+
# params - converted to query or request body
|
43
|
+
# headers - passed on to the request
|
44
|
+
def options(new_options = nil)
|
45
|
+
return @options if new_options.nil?
|
46
|
+
ApiClient::Utils.deep_merge(@options, new_options)
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def params(options = nil)
|
51
|
+
return @params if options.nil?
|
52
|
+
ApiClient::Utils.deep_merge(@params, options) if options
|
53
|
+
self
|
54
|
+
end
|
55
|
+
alias :scope :params
|
56
|
+
|
57
|
+
def headers(options = nil)
|
58
|
+
return @headers if options.nil?
|
59
|
+
ApiClient::Utils.deep_merge(@headers, options) if options
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def raw_body(options = nil)
|
64
|
+
return @raw_body if options.nil?
|
65
|
+
@raw_body = options
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def clone_only_headers
|
70
|
+
self.class.new(self.scopeable).headers(self.headers)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Half-level :)
|
74
|
+
# This is a swiss-army knife kind of method, extremely useful
|
75
|
+
def fetch(path, options = {})
|
76
|
+
scoped(self) do
|
77
|
+
@scopeable.build get(path, options)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Low-level connection methods
|
82
|
+
|
83
|
+
def request(method, path, options = {})
|
84
|
+
options = options.dup
|
85
|
+
|
86
|
+
raw = raw? || options.delete(:raw)
|
87
|
+
params(options)
|
88
|
+
response = connection.send method, path, (@raw_body || @params), @headers
|
89
|
+
raw ? response : @scopeable.parse(response)
|
90
|
+
end
|
91
|
+
|
92
|
+
def get(path, options = {})
|
93
|
+
request(:get, path, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def post(path, options = {})
|
97
|
+
request(:post, path, options)
|
98
|
+
end
|
99
|
+
|
100
|
+
def patch(path, options = {})
|
101
|
+
request(:patch, path, options)
|
102
|
+
end
|
103
|
+
|
104
|
+
def put(path, options = {})
|
105
|
+
request(:put, path, options)
|
106
|
+
end
|
107
|
+
|
108
|
+
def delete(path, options = {})
|
109
|
+
request(:delete, path, options)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Dynamic delegation of scopeable methods
|
113
|
+
def method_missing(method, *args, &block)
|
114
|
+
if @scopeable.respond_to?(method)
|
115
|
+
@scopeable.scoped(self) do
|
116
|
+
@scopeable.send(method, *args, &block)
|
117
|
+
end
|
118
|
+
else
|
119
|
+
super
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|