ezapi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f9f560c99144dec1335c0fff65c18ab7b8d2e45a
4
+ data.tar.gz: 4a745539e16f2d9f0ca262debc00422474204029
5
+ SHA512:
6
+ metadata.gz: 1d08ba48dc7f31f0b568e0bbb0dab02e1491fe02d07c0052b0b928b69d2b6dd7bf94eddf022d6c5f4702f428ce557fbc24cd03149fbc3d560a27d1ca4509f6b2
7
+ data.tar.gz: 1afd0a7847788a23b57ec3d9744b983d39370069ed0a81270fb870118387a9d7d7323005e9a2de1ff22a6cd81d5788cb10bf7fb8149fda3af92db3f7082554f6
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ *.gem
14
+ *.rbc
15
+
16
+ # Ignore bundler config
17
+ /.bundle
18
+ Gemfile.lock
19
+ .ruby-version
20
+ .ruby-gemset
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.7
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in ezapi.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # EZApi
2
+
3
+ EZApi makes interfacing with third party APIs easy! EZApi handles network requests, responses, and serializes the object into an easy to use ruby object. Standard RESTful actions are included out of the box and you can easily add custom actions.
4
+
5
+ ## Usage
6
+
7
+ Install the gem
8
+ ```ruby
9
+ gem 'ezapi'
10
+ ```
11
+
12
+ Define your API client & models
13
+ ```ruby
14
+ module ThirdPartyApp
15
+ extend EZApi::Client
16
+
17
+ api_url 'http://www.example.com/api/' # => / at the end is required
18
+ api_key 'XXXXXX' # => required
19
+
20
+
21
+ class User < EZApi::ObjectBase
22
+ path 'foo' # => to create custom path. Defaults to 'users' in this case
23
+ actions [:show, :create, :delete, :update, :index] # => Included RESTful actions
24
+ end
25
+ end
26
+ ```
27
+
28
+ Now we can call `http://www.example.com/api/users` endpoints
29
+ ```ruby
30
+ begin
31
+ user = ThirdPartyApp::User.show('123')
32
+ user.first_name # => returns first_name json field
33
+ user.first_name = 'new name'
34
+ user.save
35
+
36
+ ThirdPartyApp::User.delete('123')
37
+ ThirdPartyApp::User.update({first_name: 'Name'})
38
+ ThirdPartyApp::User.index({page: 1})
39
+ rescue EZApiError => e
40
+ print(e.message)
41
+ end
42
+ ```
43
+
44
+
45
+ ### Custom actions
46
+
47
+ There are two ways of adding custom actions that are not in the included rest
48
+ ```ruby
49
+ module ThirdPartyApp
50
+ class User < EZApi::ObjectBase
51
+
52
+ # For class methods
53
+ module ClassMethods
54
+ def custom_action
55
+ response = client.get("#{api_path}/custom_action")
56
+ # Do whatever you want with the response
57
+ end
58
+ end
59
+
60
+ def self.included(base)
61
+ base.extend(ClassMethods)
62
+ end
63
+
64
+ def custom_instance_action
65
+ response = self.class.client.get("#{self.class.api_path}/custom_action")
66
+ # Do whatever you want with the response
67
+ end
68
+ end
69
+ end
70
+
71
+
72
+ ThirdPartyApp::User.custom_action
73
+ ThirdPartyApp::User.new.custom_instance_action
74
+ ```
75
+
76
+
77
+ #### Add custom actions to actions array
78
+ ```ruby
79
+ module ThirdPartyApp
80
+ module Actions
81
+ module MyCustomAction
82
+ def my_custom_action
83
+ # Do Something
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ module ThirdPartyApp
90
+ class User < EZApi::ObjectBase
91
+ actions [:my_custom_action]
92
+ end
93
+ end
94
+
95
+ ```
96
+ You can even override the include REST actions using this method
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ezapi"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/ezapi.gemspec ADDED
@@ -0,0 +1,38 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "ezapi/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ezapi"
8
+ spec.version = EZApi::VERSION
9
+ spec.authors = ["Nicholas"]
10
+
11
+ spec.summary = "Easily Communicate with APIs"
12
+ spec.description = "Wrapper around interacting with APIs"
13
+ spec.homepage = "https://github.com/nbwar/ezapi"
14
+
15
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+ # if spec.respond_to?(:metadata)
18
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
19
+ # else
20
+ # raise "RubyGems 2.0 or newer is required to protect against " \
21
+ # "public gem pushes."
22
+ # end
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
25
+ f.match(%r{^(test|spec|features)/})
26
+ end
27
+
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency 'rest-client', '~> 1.8'
33
+ spec.add_dependency 'activesupport', '>= 4.2'
34
+ spec.add_development_dependency "bundler", "~> 1.16"
35
+ spec.add_development_dependency "rake", "~> 10.0"
36
+ spec.add_development_dependency "rspec", "~> 3.0"
37
+ spec.add_development_dependency "pry", "~> 0.10.4"
38
+ end
@@ -0,0 +1,17 @@
1
+ module EZApi
2
+ module Actions
3
+ module Create
4
+ module ClassMethods
5
+ def create(params = {})
6
+ obj = new(params)
7
+ obj.save
8
+ obj
9
+ end
10
+ end
11
+
12
+ def self.included(base)
13
+ base.extend(ClassMethods)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module EZApi
2
+ module Actions
3
+ module Delete
4
+ module ClassMethods
5
+ def delete(id)
6
+ response = client.delete("#{api_path}/#{id}")
7
+ true
8
+ end
9
+ end
10
+
11
+ def delete
12
+ if id
13
+ self.class.client.delete("#{self.class.api_path}/#{id}")
14
+ true
15
+ end
16
+ end
17
+
18
+ def self.included(base)
19
+ base.extend(ClassMethods)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ module EZApi
2
+ module Actions
3
+ module Index
4
+ module ClassMethods
5
+ def index(query_params, params = {})
6
+ response = client.get(list_path(query_params), params)
7
+ response.compact.map { |item| self.new(item) }
8
+ end
9
+
10
+ private
11
+ def list_path(query_params)
12
+ [api_path, build_query_params(query_params)].compact.join('?')
13
+ end
14
+
15
+ def build_query_params(query_params)
16
+ query_params && query_params
17
+ .collect { |key, value| "#{key}=#{URI.encode_www_form_component(value)}" }
18
+ .join('&')
19
+ end
20
+ end
21
+
22
+ def self.included(base)
23
+ base.extend(ClassMethods)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ module EZApi
2
+ module Actions
3
+ module Save
4
+ def save
5
+ response = self.class.client.send(request_type, save_path, as_json)
6
+ assign_attributes(response) if response
7
+ true
8
+ end
9
+
10
+ private
11
+
12
+ def request_type
13
+ id ? :put : :post
14
+ end
15
+
16
+ def save_path
17
+ [self.class.api_path, id].compact.join('/')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ module EZApi
2
+ module Actions
3
+ module Show
4
+ module ClassMethods
5
+ def show(id)
6
+ response = client.get("#{api_path}/#{id}")
7
+ self.new(response)
8
+ end
9
+ end
10
+
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module EZApi
2
+ module Actions
3
+ module Update
4
+ module ClassMethods
5
+ def update(id, params)
6
+ new(params.merge(id: id)).save
7
+ end
8
+ end
9
+
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,92 @@
1
+ require 'rest-client'
2
+ require 'json'
3
+ require 'base64'
4
+ require 'uri'
5
+
6
+ module EZApi
7
+ module Client
8
+ attr_accessor :key, :base_url
9
+
10
+ def self.extended(base)
11
+ [:get, :post, :put, :patch, :delete].each do |method|
12
+ define_method(method) do |path, params = {}|
13
+ full_path = full_api_path(path)
14
+ request(full_path, :put, params)
15
+ end
16
+ end
17
+ end
18
+
19
+ def api_url(url)
20
+ self.base_url = url
21
+ end
22
+
23
+ def api_key(key)
24
+ self.key = key
25
+ end
26
+
27
+ def request(full_url, method, params={})
28
+ raise(AuthenticationError, "API key is not set for #{self.app_name}.") unless self.key
29
+
30
+ begin
31
+ response = RestClient::Request.execute(method: method, url: full_url, payload: params.to_json, headers: request_headers)
32
+ JSON.parse(response) unless response.empty?
33
+ rescue RestClient::ExceptionWithResponse => e
34
+ if response_code = e.http_code and response_body = e.http_body
35
+ handle_api_error(response_code, JSON.parse(response_body))
36
+ else
37
+ handle_restclient_error(e)
38
+ end
39
+ rescue RestClient::Exception, Errno::ECONNREFUSED => e
40
+ handle_restclient_error(e)
41
+ end
42
+ end
43
+
44
+ protected
45
+ def app_name
46
+ self.name.demodulize
47
+ end
48
+
49
+ def full_api_path(path)
50
+ URI.join(base_url, path).to_s
51
+ end
52
+
53
+ def request_headers
54
+ {
55
+ Authorization: "Basic #{encoded_api_key}",
56
+ content_type: :json,
57
+ accept: :json
58
+ }
59
+ end
60
+
61
+ def encoded_api_key
62
+ Base64.urlsafe_encode64(self.key)
63
+ end
64
+
65
+ def parse_error_message(body)
66
+ body['message']
67
+ end
68
+
69
+ def handle_api_error(code, body)
70
+ message = parse_error_message(body)
71
+ case code
72
+ when 400, 404
73
+ raise(InvalidRequestError, message)
74
+ when 401
75
+ raise(AuthenticationError, message)
76
+ else
77
+ raise(ApiError, message)
78
+ end
79
+ end
80
+
81
+ def handle_restclient_error(e)
82
+ case e
83
+ when RestClient::ServerBrokeConnection
84
+ message = "The connection with #{app_name} terminated before the request completed."
85
+ else
86
+ message = "Could not connect to #{app_name}."
87
+ end
88
+
89
+ raise(ConnectionError, message)
90
+ end
91
+ end
92
+ end
data/lib/ezapi/dsl.rb ADDED
@@ -0,0 +1,43 @@
1
+ module EZApi
2
+ module DSL
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def path(url)
9
+ define_singleton_method(:api_path) { url }
10
+ end
11
+
12
+ def actions(actions)
13
+ actions.each do |action|
14
+ include_action(action)
15
+ end
16
+
17
+ if (actions & [:create, :update]).any? && !actions.include?(:save)
18
+ include_action(:save)
19
+ end
20
+ end
21
+
22
+ def client
23
+ client_name = self.name.deconstantize
24
+ @client ||= !client_name.empty? ? client_name.constantize : EZApi::Client
25
+ end
26
+
27
+ def api_path
28
+ path = self.name.demodulize.underscore.pluralize
29
+ "#{CGI.escape(path)}"
30
+ end
31
+
32
+ private
33
+ def include_action(action)
34
+ begin
35
+ include client::Actions.const_get(action.to_s.camelize)
36
+ rescue NameError
37
+ include EZApi::Actions.const_get(action.to_s.camelize)
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ module EZApi
2
+ class EZApiError < StandardError;end
3
+ class AuthenticationError < EZApiError; end
4
+ class InvalidRequestError < EZApiError; end
5
+ class ConnectionError < EZApiError; end
6
+ class ApiError < EZApiError; end
7
+ end
@@ -0,0 +1,53 @@
1
+ module EZApi
2
+ class ObjectBase
3
+ include EZApi::DSL
4
+
5
+ attr_reader :params
6
+
7
+ def initialize(params={})
8
+ assign_attributes(params)
9
+ end
10
+
11
+ def id
12
+ attributes['id']
13
+ end
14
+
15
+ def id=(value)
16
+ attributes['id'] = value
17
+ end
18
+
19
+ def as_json(*options)
20
+ attributes.as_json(*options)
21
+ end
22
+
23
+ private
24
+ def assign_attributes(params)
25
+ params.each do |key, value|
26
+ key = key.to_s.underscore
27
+ define_attribute_accessors(key) unless respond_to?(key)
28
+
29
+ # TODO: Support creating real api objects based on associations
30
+ case value
31
+ when Array
32
+ value = value.map do |obj|
33
+ obj.is_a?(Hash) ? ObjectBase.new(obj) : obj
34
+ end
35
+ when Hash
36
+ value = ObjectBase.new(value)
37
+ end
38
+
39
+ public_send(:"#{key}=", value)
40
+ end
41
+ end
42
+
43
+ def define_attribute_accessors(attr)
44
+ attr = attr.to_s
45
+ define_singleton_method(:"#{attr}=") { |value| attributes[attr] = value }
46
+ define_singleton_method(attr) { attributes[attr] }
47
+ end
48
+
49
+ def attributes
50
+ @attributes ||= {}
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module EZApi
2
+ VERSION = "0.1.0"
3
+ end
data/lib/ezapi.rb ADDED
@@ -0,0 +1,22 @@
1
+ # Dependencies
2
+ require 'active_support/core_ext/string/inflections'
3
+ require 'active_support/json'
4
+ require 'active_support/core_ext/object/json'
5
+
6
+ # Main
7
+ require "ezapi/version"
8
+ require "ezapi/errors"
9
+ require "ezapi/client"
10
+ require "ezapi/dsl"
11
+ require "ezapi/object_base"
12
+
13
+ # Actions
14
+ require "ezapi/actions/show"
15
+ require "ezapi/actions/save"
16
+ require "ezapi/actions/create"
17
+ require "ezapi/actions/delete"
18
+ require "ezapi/actions/update"
19
+ require "ezapi/actions/index"
20
+
21
+ module EZApi
22
+ end
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ezapi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nicholas
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-10-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '4.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.16'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.16'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.10.4
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.10.4
97
+ description: Wrapper around interacting with APIs
98
+ email:
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - ".gitignore"
104
+ - ".rspec"
105
+ - ".travis.yml"
106
+ - Gemfile
107
+ - README.md
108
+ - Rakefile
109
+ - bin/console
110
+ - bin/setup
111
+ - ezapi.gemspec
112
+ - lib/ezapi.rb
113
+ - lib/ezapi/actions/create.rb
114
+ - lib/ezapi/actions/delete.rb
115
+ - lib/ezapi/actions/index.rb
116
+ - lib/ezapi/actions/save.rb
117
+ - lib/ezapi/actions/show.rb
118
+ - lib/ezapi/actions/update.rb
119
+ - lib/ezapi/client.rb
120
+ - lib/ezapi/dsl.rb
121
+ - lib/ezapi/errors.rb
122
+ - lib/ezapi/object_base.rb
123
+ - lib/ezapi/version.rb
124
+ homepage: https://github.com/nbwar/ezapi
125
+ licenses: []
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.6.14
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: Easily Communicate with APIs
147
+ test_files: []