Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sawyer committed Apr 25, 2013
0 parents commit 301c7b7
Show file tree
Hide file tree
Showing 23 changed files with 691 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .gitignore
@@ -0,0 +1,27 @@
*.gem
*.rbc
.bundle
.config
coverage
InstalledFiles
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp

# YARD artifacts
.yardoc
_yardoc
doc/

# iOS
.DS_Store
.AppleDouble
.LSOverride
Icon
._*
.Spotlight-V100
.Trashes
Empty file added CHANGELOG
Empty file.
2 changes: 2 additions & 0 deletions Gemfile
@@ -0,0 +1,2 @@
source 'https://rubygems.org'
gemspec
27 changes: 27 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,27 @@
PATH
remote: .
specs:
easypost (1.2)
multi_json (>= 1.0.4, < 2)
rest-client (~> 1.4)
typhoeus (~> 0.5.3)

GEM
remote: https://rubygems.org/
specs:
ethon (0.5.12)
ffi (>= 1.3.0)
mime-types (~> 1.18)
ffi (1.8.1)
mime-types (1.23)
multi_json (1.7.2)
rest-client (1.6.7)
mime-types (>= 1.16)
typhoeus (0.5.4)
ethon (~> 0.5.7)

PLATFORMS
ruby

DEPENDENCIES
easypost!
21 changes: 21 additions & 0 deletions LICENSE
@@ -0,0 +1,21 @@
The MIT License

Copyright (c) 2013 EasyPost (Simpler Postage, Inc)

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.
31 changes: 31 additions & 0 deletions README.md
@@ -0,0 +1,31 @@
Installation
---------------

Build the gem:

```
gem build easypost.gemspec
```

Import the EasyPost client:

```
require 'easypost'
```

Example
------------------

```ruby
Coming soon!
```

Documentation
--------------------

Up-to-date documentation at: https://www.geteasypost.com/docs

Tests
--------------------

Coming soon!
9 changes: 9 additions & 0 deletions Rakefile
@@ -0,0 +1,9 @@
task :default => [:test]

task :test do
ret = true
Dir["test/**/*.rb"].each do |f|
ret = ret && ruby(f, '')
end
exit(ret)
end
1 change: 1 addition & 0 deletions VERSION
@@ -0,0 +1 @@
1.2
7 changes: 7 additions & 0 deletions bin/easypost-irb
@@ -0,0 +1,7 @@
#!/usr/bin/env ruby
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'

libs = " -r irb/completion"
libs << " -r #{File.dirname(__FILE__) + '/../lib/easypost'}"
puts "Initializing EasyPost..."
exec "#{irb} #{libs} --simple-prompt"
24 changes: 24 additions & 0 deletions easypost.gemspec
@@ -0,0 +1,24 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'easypost/version'

Gem::Specification.new do |spec|
spec.name = 'easypost'
spec.version = EasyPost::VERSION
spec.date = '2013-04-23'
spec.summary = 'EasyPost Ruby client library'
spec.description = 'Client library for accessing the EasyPost shipping API via Ruby.'
spec.authors = ['Jon Calhoun', 'Sawyer Bateman']
spec.email = 'contact@easypost.co'
spec.homepage = 'https://www.geteasypost.com/docs'

spec.files = `git ls-files`.split("\n")
spec.test_files = `git ls-files -- test/*`.split("\n")
spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
spec.require_path = 'lib'

spec.add_dependency('typhoeus', ["~> 0.5.3"])
spec.add_dependency('rest-client', '~> 1.4')
spec.add_dependency('multi_json', '>= 1.0.4', '< 2')
end
159 changes: 159 additions & 0 deletions lib/easypost.rb
@@ -0,0 +1,159 @@
require 'cgi'
require 'set'
require 'openssl'
require 'rest_client'
require 'multi_json'

# Resources
require 'easypost/util'
require 'easypost/object'
require 'easypost/resource'
require 'easypost/address'
require 'easypost/scan_form'

# Errors
require 'easypost/errors/easypost_error'
require 'easypost/errors/api_error'
require 'easypost/errors/network_error'
require 'easypost/errors/invalid_request_error'
require 'easypost/errors/authentication_error'

module EasyPost
@@api_key = nil
# @@api_base = 'https://www.geteasypost.com/api/v2'
@@api_base = 'http://localhost:5000/api/v2'
@@api_version = nil

def self.api_url(url='')
@@api_base + url
end

def self.api_key=(api_key)
@@api_key = api_key
end

def self.api_key
@@api_key
end

def self.api_base=(api_base)
@@api_base = api_base
end

def self.api_base
@@api_base
end

def self.api_version=(version)
@@api_version = version
end

def self.api_version
@@api_version
end

def self.request(method, url, api_key, params={}, headers={})
api_key ||= @@api_key
raise AuthenticationError.new('No API key provided. (HINT: set your API key using "EasyPost.api_key = <API-KEY>". You can generate API keys from the EasyPost web interface. See https://www.geteasypost.com/docs for details, or email contact@easypost.co if you have any questions.)') unless api_key

ssl_opts = { :verify_ssl => false }

params = Util.objects_to_ids(params)
url = self.api_url(url)
case method.to_s.downcase.to_sym
when :get, :head, :delete
# Make params into GET parameters
if params && params.count > 0
query_string = Util.flatten_params(params).collect{|key, value| "#{key}=#{Util.url_encode(value)}"}.join('&')
url += "#{URI.parse(url).query ? '&' : '?'}#{query_string}"
end
payload = nil
else
payload = Util.flatten_params(params).collect{|(key, value)| "#{key}=#{Util.url_encode(value)}"}.join('&')
end

puts url
puts payload
puts '******************'

headers = {
:user_agent => "EasyPost/v2 RubyClient/1.2",
:authorization => "Bearer #{api_key}",
:content_type => 'application/x-www-form-urlencoded'
}.merge(headers)

opts = {
:method => method,
:url => url,
:headers => headers,
:open_timeout => 30,
:payload => payload,
:timeout => 60
}.merge(ssl_opts)

begin
response = execute_request(opts)
rescue RestClient::ExceptionWithResponse => e
if response_code = e.http_code and response_body = e.http_body
self.handle_api_error(response_code, response_body)
else
self.handle_network_error(e)
end
rescue RestClient::Exception, Errno::ECONNREFUSED => e
self.handle_network_error(e)
end

begin
response_json = MultiJson.load(response.body, :symbolize_keys => true)
rescue MultiJson::DecodeError
raise APIError.new("Invalid response object from API: #{response.body.inspect} (HTTP response code was #{response.code})", response.code, response.body)
end

return [response_json, api_key]
end

private

def self.execute_request(opts)
RestClient::Request.execute(opts)
end

def self.handle_api_error(response_code, response_body)
begin
error_obj = MultiJson.load(response_body, :symbolize_keys => true)
# error_obj = Util.symbolize_names(error_obj)
error = error_obj[:error] or raise EasyPostError.new # escape from parsing
rescue MultiJson::DecodeError, EasyPostError
raise APIError.new("Invalid response object from API: #{response_body.inspect} (HTTP response code was #{response_code})", response_code, response_body)
end

case response_code
when 400, 404 then
raise invalid_request_error(error, response_code, response_body, error_obj)
when 401
raise authentication_error(error, response_code, response_body, error_obj)
when 402
raise card_error(error, response_code, response_body, error_obj)
else
raise api_error(error, response_code, response_body, error_obj)
end
end

def self.invalid_request_error(error, response_code, response_body, error_obj)
InvalidRequestError.new(error[:message], error[:param], response_code, response_body, error_obj)
end

def self.authentication_error(error, response_code, response_body, error_obj)
AuthenticationError.new(error[:message], response_code, response_body, error_obj)
end

def self.api_error(error, response_code, response_body, error_obj)
APIError.new(error[:message], response_code, response_body, error_obj)
end

def self.handle_network_error(e)

message += "\n\n(Network error: #{e.message})"
raise NetworkError.new(message)
end
end
11 changes: 11 additions & 0 deletions lib/easypost/address.rb
@@ -0,0 +1,11 @@
module EasyPost
class Address < Resource

def verify(params={})
response, api_key = EasyPost.request(:post, url + '/verify', @api_key, params)
refresh_from(response, api_key)
self
end

end
end
4 changes: 4 additions & 0 deletions lib/easypost/errors/api_error.rb
@@ -0,0 +1,4 @@
module EasyPost
class APIError < EasyPostError
end
end
4 changes: 4 additions & 0 deletions lib/easypost/errors/authentication_error.rb
@@ -0,0 +1,4 @@
module EasyPost
class AuthenticationError < EasyPostError
end
end
20 changes: 20 additions & 0 deletions lib/easypost/errors/easypost_error.rb
@@ -0,0 +1,20 @@
module EasyPost
class EasyPostError < StandardError
attr_reader :message
attr_reader :http_status
attr_reader :http_body
attr_reader :json_body

def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
@message = message
@http_status = http_status
@http_body = http_body
@json_body = json_body
end

def to_s
status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
"#{status_string}#{@message}"
end
end
end
10 changes: 10 additions & 0 deletions lib/easypost/errors/invalid_request_error.rb
@@ -0,0 +1,10 @@
module EasyPost
class InvalidRequestError < EasyPostError
attr_accessor :param

def initialize(message, param, http_status=nil, http_body=nil, json_body=nil)
super(message, http_status, http_body, json_body)
@param = param
end
end
end
4 changes: 4 additions & 0 deletions lib/easypost/errors/network_error.rb
@@ -0,0 +1,4 @@
module EasyPost
class NetworkError < EasyPostError
end
end

0 comments on commit 301c7b7

Please sign in to comment.