Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tobischo committed Sep 30, 2015
0 parents commit a5ca24c
Show file tree
Hide file tree
Showing 17 changed files with 973 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.DS_Store
*.gem
*.rbc
.bundle
.config
.yardoc
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
2 changes: 2 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--color
--require spec_helper
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: ruby
rvm:
- 2.1.0
script:
- bundle exec rspec
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FROM dreg.barzahlen.de/rubygem:0.1
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source 'http://rubygems.org'

# Specify your gem's dependencies in the .gemspec file
gemspec
49 changes: 49 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
PATH
remote: .
specs:
grac (1.0.0)
addressable (~> 2.3.8)
typhoeus (~> 0.6.9)

GEM
remote: http://rubygems.org/
specs:
addressable (2.3.8)
builder (3.2.2)
diff-lcs (1.2.5)
ethon (0.8.0)
ffi (>= 1.3.0)
ffi (1.9.10)
rack (1.6.4)
rack-test (0.6.3)
rack (>= 1.0)
rake (10.4.2)
rspec (3.2.0)
rspec-core (~> 3.2.0)
rspec-expectations (~> 3.2.0)
rspec-mocks (~> 3.2.0)
rspec-core (3.2.3)
rspec-support (~> 3.2.0)
rspec-expectations (3.2.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.2.0)
rspec-mocks (3.2.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.2.0)
rspec-support (3.2.2)
rspec_junit_formatter (0.2.3)
builder (< 4)
rspec-core (>= 2, < 4, != 2.12.0)
typhoeus (0.6.9)
ethon (>= 0.7.1)

PLATFORMS
ruby

DEPENDENCIES
builder (~> 3.2.2)
grac!
rack-test (~> 0.6.3)
rake (~> 10.4.1)
rspec (~> 3.2.0)
rspec_junit_formatter (~> 0.2.2)
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2015 Cash Payment Solutions GmbH

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
9 changes: 9 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "bundler/gem_tasks"

task :c do
require 'irb'
require 'irb/completion'
require './lib/grac.rb'
ARGV.clear
IRB.start
end
30 changes: 30 additions & 0 deletions grac.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'grac/version'

Gem::Specification.new do |spec|
# For explanations see http://docs.rubygems.org/read/chapter/20
spec.name = "grac"
spec.version = Grac::VERSION
spec.authors = ["Tobias Schoknecht"]
spec.email = ["[email protected]"]
spec.description = %q{Generic REST API Client}
spec.summary = %q{Very generic client for REST API with basic error handling}
spec.homepage = "https://github.com/Barzahlen/grac"
spec.license = "MIT"

spec.files = Dir['lib/**/*.rb']
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]

spec.add_development_dependency "rake", "~> 10.4.1"
spec.add_development_dependency "rspec", "~> 3.2.0"
spec.add_development_dependency "builder", "~> 3.2.2" # Needed for ci-reporter
spec.add_development_dependency "rspec_junit_formatter", "~> 0.2.2"
spec.add_development_dependency "rack-test", "~> 0.6.3"

spec.add_runtime_dependency "addressable", "~> 2.3.8"
spec.add_runtime_dependency "typhoeus", "~> 0.6.9"
end
1 change: 1 addition & 0 deletions lib/grac.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require 'grac/client'
173 changes: 173 additions & 0 deletions lib/grac/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
require 'addressable/template'
require 'addressable/uri'
require 'json'
require 'typhoeus'

require_relative './exception'

module Grac
class Client
def initialize(options = {})
if !options["uri"].nil? && !options["uri"].empty?
uri = Addressable::URI.parse(options["uri"])
options["scheme"] ||= uri.scheme
options["host"] ||= uri.host
options["port"] ||= uri.port
options["path"] ||= uri.path
end

@options = {
"scheme" => options["scheme"] || 'http',
"host" => options["host"] || 'localhost',
"port" => options["port"] || 80,
"path" => options["path"] || '/',
"connecttimeout" => options["connecttimeout"] || 0.1,
"timeout" => options["timeout"] || 15,
"params" => options["params"] || {},
"headers" => { "User-Agent" => "Grac v#{Grac::VERSION}" }.merge(options["headers"] || {})
}

@options["path"] = Addressable::URI.join("/", @options["path"]).path
end

def set(options = {})
self.class.new(@options.merge(options))
end

def set!(options = {})
@options.merge!(options)
return self
end

def method_missing(m, *args, &block)
chain = true
if m =~ /\A(.+)!\z/
chain = false
m = $1
end

path = join_to_path(m)

if chain
return self.class.new(@options.merge({ "path" => path }))
else
@options["path"] = path
return self
end
end

# https://robots.thoughtbot.com/always-define-respond-to-missing-when-overriding
def respond_to_missing?(method_name, include_private = false)
private_methods.include?(method_name.to_sym) ? super : true
end

# Defines var, var!, type, type!, expand, expand!, partial_expand and partial_expand!
%w{var type expand partial_expand}.each do |method|
define_method method do |param|
return self.class.new(@options.merge({ "path" => send("#{method}_logic", param) }))
end

define_method "#{method}!" do |param|
@options["path"] = send("#{method}_logic", param)
return self
end
end

def uri
"#{@options["scheme"]}://#{@options["host"]}:#{@options["port"]}#{@options["path"]}"
end

%w{post put patch}.each do |method|
define_method method do |body = {}, params = {}|
request = build_request(method, { "body" => body, "params" => params })
handle_response(request)
end
end

%w{get delete}.each do |method|
define_method method do |params = {}|
request = build_request(method, { "params" => params })
handle_response(request)
end
end

private
def var_logic(var)
return join_to_path(var)
end

def type_logic(type)
return join_to_path(type, ".")
end

def expand_logic(options)
return Addressable::Template.new(@options["path"]).expand(options).path
end

def partial_expand_logic(options)
return Addressable::Template.new(@options["path"]).partial_expand(options).pattern
end

def join_to_path(value, sep = "/")
separator = @options["path"].reverse[0,1] == sep ? '' : sep
return "#{@options["path"]}#{separator}#{value}"
end

def build_request(method, options = {})
body = options["body"].nil? || options["body"].empty? ? nil : options["body"].to_json

request_hash = { :method => method }
request_hash[:params] = @options["params"].merge(options["params"] || {})
request_hash[:body] = body
request_hash[:connecttimeout] = @options["connecttimeout"]
request_hash[:timeout] = @options["timeout"]
request_hash[:headers] = @options["headers"]

return ::Typhoeus::Request.new(uri, request_hash)
end

def parse_json(body)
JSON.parse(body)
end

def handle_response(request)
response = request.run

# Retry GET and HEAD requests - modifying requests might not be idempotent
method = request.options["method"].to_s.downcase
response = request.run if response.timed_out? && ['get', 'head'].include?(method)

# A request can time out while receiving data. In this case response.code might indicate
# success although data hasn't been fully transferred. Thus rely on Typhoeus for
# detecting a timeout.
if response.timed_out?
raise Exception::ServiceTimeout.new(
"Service timed out: #{response.return_message}")
end

case response.code
when 200, 201
begin
return parse_json(response.body)
rescue JSON::ParserError
return response.body
end
when 204
return true
when 0
raise Exception::RequestFailed.new(
"Service request failed: #{response.return_message}")
when 400
raise Exception::Invalid.new(response)
when 403
raise Exception::Forbidden.new(response)
when 404
raise Exception::NotFound.new(response)
when 409
raise Exception::Conflict.new(response)
else
raise Exception::ServiceError.new(response)
end
end
end
end
41 changes: 41 additions & 0 deletions lib/grac/exception.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require 'json'

module Grac
module Exception
class ClientException < StandardError
attr_reader :http_code, :service_response, :object, :error, :message, :errors

def initialize(response)
@http_code = response.code
@service_response = parse_json(response.body)
@object = @service_response["object"].to_sym if @service_response["object"]
@error = @service_response["error"].to_sym if @service_response["error"]
@message = @service_response["message"]
@errors = @service_response["errors"] || {}
end

def inspect
"#{self.class.name}: #{@service_response}"
end

def to_s
@message.nil? || @message.empty? ? self.class.name : @message
end

private

def parse_json(body)
JSON.parse(body)
end

end

class Invalid < ClientException; end
class Forbidden < ClientException; end
class NotFound < ClientException; end
class Conflict < ClientException; end
class ServiceError < ClientException; end
class RequestFailed < StandardError; end
class ServiceTimeout < RequestFailed; end
end
end
3 changes: 3 additions & 0 deletions lib/grac/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Grac
VERSION = "1.0.0"
end
Loading

0 comments on commit a5ca24c

Please sign in to comment.