postmaster 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ # Postmaster
2
+
3
+ Developer Friendly Shipping
4
+
5
+ Postmaster takes the pain out of sending shipments via UPS, Fedex, and USPS.
6
+ Save money before you ship, while you ship, and after you ship.
7
+
8
+ https://www.postmaster.io/
9
+
10
+ ## Requirements
11
+
12
+ - [Ruby](http://www.ruby-lang.org/) 1.8.7 or above.
13
+ - [rest-client](https://github.com/archiloque/rest-client) 1.4 or above.
14
+ - [multi_json](https://github.com/intridea/multi_json) 1.0.2 or above.
15
+
16
+ ## Installation
17
+
18
+ You can install using [gem](#gem) or from [source](#source).
19
+
20
+ ### Gem
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ gem 'postmaster'
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install postmaster
33
+
34
+ ### Source
35
+
36
+ Download the postmaster-ruby source:
37
+
38
+ $ git clone https://github.com/postmaster/postmaster-ruby
39
+
40
+ If you want to build the gem from source:
41
+
42
+ $ cd postmaster-ruby
43
+ $ gem build postmaster.gemspec
44
+
45
+ ## Usage
46
+
47
+ See https://postmaster.io/docs for tutorials and documentation.
48
+
49
+ ## Issues
50
+
51
+ Please use appropriately tagged github [issues](https://github.com/postmaster/postmaster-api/issues)
52
+ to request features or report bugs.
53
+
54
+ ## Testing
55
+
56
+ $ cd tests
57
+ $ PM_API_KEY=your-api-key rake test
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'rake/testtask'
3
+ require 'rcov/rcovtask'
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ Rcov::RcovTask.new do |t|
12
+ t.test_files = FileList['test/test_*.rb']
13
+ #t.verbose = true # uncomment to see the executed command
14
+ end
15
+
16
+
17
+ task :default => [:test]
18
+
@@ -0,0 +1,42 @@
1
+ require "postmaster"
2
+
3
+ Postmaster.api_key = "example-api-key"
4
+
5
+ result = Postmaster::AddressValidation.validate(
6
+ :company => "ASLS",
7
+ :contact => "Joe Smith",
8
+ :line1 => "1110 Someplace Ave.",
9
+ :city => "Austin",
10
+ :state => "TX",
11
+ :zip => "78704",
12
+ :country => "US"
13
+ )
14
+
15
+ result = Postmaster::Shipment.create(
16
+ :to => {
17
+ :company => "ASLS",
18
+ :contact => "Joe Smith",
19
+ :line1 => "1110 Someplace Ave.",
20
+ :city => "Austin",
21
+ :state => "TX",
22
+ :zip_code => "78704",
23
+ :phone_no => "919-720-7941",
24
+ :country => "US"
25
+ },
26
+ :carrier => "ups",
27
+ :service => "2DAY",
28
+ :package => {
29
+ :value => 55,
30
+ :weight => 1.5,
31
+ :length => 10,
32
+ :width => 6,
33
+ :height => 8,
34
+ },
35
+ :reference => "Order # 54321"
36
+ )
37
+
38
+ shipment = Postmaster::Shipment.retrieve(1)
39
+ result = shipment.track()
40
+
41
+ shipment = Postmaster::Shipment.retrieve(1)
42
+ result = shipment.void()
@@ -0,0 +1,208 @@
1
+ # Postmaster Ruby bindings
2
+ require 'pp'
3
+ require 'cgi'
4
+ require 'set'
5
+ require 'rubygems'
6
+ require 'openssl'
7
+
8
+ gem 'rest-client', '~> 1.4'
9
+ require 'rest_client'
10
+ require 'multi_json'
11
+
12
+ # Version
13
+ require 'postmaster/version'
14
+
15
+ # API operations
16
+ require 'postmaster/api_operations/create'
17
+ require 'postmaster/api_operations/list'
18
+
19
+ # Resources
20
+ require 'postmaster/util'
21
+ require 'postmaster/json'
22
+ require 'postmaster/postmaster_object'
23
+ require 'postmaster/api_resource'
24
+
25
+ require 'postmaster/address'
26
+ require 'postmaster/shipment'
27
+ require 'postmaster/package'
28
+ require 'postmaster/tracking'
29
+
30
+ # Errors
31
+ require 'postmaster/errors/postmaster_error'
32
+ require 'postmaster/errors/api_error'
33
+ require 'postmaster/errors/api_connection_error'
34
+ require 'postmaster/errors/invalid_request_error'
35
+ require 'postmaster/errors/authentication_error'
36
+
37
+
38
+ module Postmaster
39
+ @@api_key = nil
40
+ @@api_base = 'https://api.postmaster.io'
41
+
42
+ def self.api_url(url='')
43
+ self.api_base + url
44
+ end
45
+
46
+ def self.api_key=(api_key)
47
+ @@api_key = api_key
48
+ end
49
+
50
+ def self.api_key
51
+ @@api_key
52
+ end
53
+
54
+ def self.api_base=(api_base)
55
+ @@api_base = api_base
56
+ end
57
+
58
+ def self.api_base
59
+ @@api_base
60
+ end
61
+
62
+ def self.request(method, url, params={}, headers={})
63
+ api_key = self.api_key
64
+ raise AuthenticationError.new('No API key provided. (HINT: set your API key using "Postmaster.api_key = <API-KEY>".)') unless api_key
65
+
66
+ uname = (@@uname ||= RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil)
67
+ lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
68
+ ua = {
69
+ :bindings_version => Postmaster::VERSION,
70
+ :lang => 'ruby',
71
+ :lang_version => lang_version,
72
+ :platform => RUBY_PLATFORM,
73
+ :publisher => 'postmaster',
74
+ :uname => uname
75
+ }
76
+
77
+ params = Util.objects_to_ids(params)
78
+ url = self.api_url(url)
79
+ case method.to_s.downcase.to_sym
80
+ when :get, :head, :delete
81
+ # Make params into GET parameters
82
+ if params && params.count > 0
83
+ query_string = Util.flatten_params(params).collect{|key, value| "#{key}=#{Util.url_encode(value)}"}.join('&')
84
+ url += "?#{query_string}"
85
+ end
86
+ payload = nil
87
+ else
88
+ payload = Util.flatten_params(params).collect{|(key, value)| "#{key}=#{Util.url_encode(value)}"}.join('&')
89
+ end
90
+
91
+ begin
92
+ headers = { :x_postmaster_client_user_agent => Postmaster::JSON.dump(ua) }.merge(headers)
93
+ rescue => e
94
+ headers = {
95
+ :x_postmaster_client_raw_user_agent => ua.inspect,
96
+ :error => "#{e} (#{e.class})"
97
+ }.merge(headers)
98
+ end
99
+
100
+ headers = {
101
+ :user_agent => "Postmaster/v1 RubyBindings/#{Postmaster::VERSION}",
102
+ :content_type => 'application/x-www-form-urlencoded'
103
+ }.merge(headers)
104
+
105
+ opts = {
106
+ :method => method,
107
+ :url => url,
108
+ :headers => headers,
109
+ :open_timeout => 60,
110
+ :payload => payload,
111
+ :timeout => 80,
112
+ :user => api_key,
113
+ :password => "",
114
+ }
115
+
116
+ begin
117
+ response = execute_request(opts)
118
+ rescue SocketError => e
119
+ self.handle_restclient_error(e)
120
+ rescue NoMethodError => e
121
+ # Work around RestClient bug
122
+ if e.message =~ /\WRequestFailed\W/
123
+ e = APIConnectionError.new('Unexpected HTTP response code')
124
+ self.handle_restclient_error(e)
125
+ else
126
+ raise
127
+ end
128
+ rescue RestClient::ExceptionWithResponse => e
129
+ if rcode = e.http_code and rbody = e.http_body
130
+ self.handle_api_error(rcode, rbody)
131
+ else
132
+ self.handle_restclient_error(e)
133
+ end
134
+ rescue RestClient::Exception, Errno::ECONNREFUSED => e
135
+ self.handle_restclient_error(e)
136
+ end
137
+
138
+ rbody = response.body
139
+ rcode = response.code
140
+ begin
141
+ # Would use :symbolize_names => true, but apparently there is
142
+ # some library out there that makes symbolize_names not work.
143
+ resp = Postmaster::JSON.load(rbody)
144
+ rescue MultiJson::DecodeError
145
+ raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})", rcode, rbody)
146
+ end
147
+
148
+ resp = Util.symbolize_names(resp)
149
+ resp
150
+ end
151
+
152
+ private
153
+
154
+ def self.execute_request(opts)
155
+ RestClient::Request.execute(opts)
156
+ end
157
+
158
+ def self.handle_api_error(rcode, rbody)
159
+ begin
160
+ error_obj = Postmaster::JSON.load(rbody)
161
+ error_obj = Util.symbolize_names(error_obj)
162
+ error = error_obj[:error] or raise PostmasterError.new # escape from parsing
163
+ rescue MultiJson::DecodeError, PostmasterError
164
+ raise APIError.new("Invalid response object from API: #{rbody.inspect} (HTTP response code was #{rcode})", rcode, rbody)
165
+ end
166
+
167
+ case rcode
168
+ when 400, 404 then
169
+ raise invalid_request_error(error, rcode, rbody, error_obj)
170
+ when 401
171
+ raise authentication_error(error, rcode, rbody, error_obj)
172
+ when 403
173
+ raise permission_error(error, rcode, rbody, error_obj)
174
+ else
175
+ raise api_error(error, rcode, rbody, error_obj)
176
+ end
177
+ end
178
+
179
+ def self.invalid_request_error(error, rcode, rbody, error_obj)
180
+ InvalidRequestError.new(error[:message], error[:param], rcode, rbody, error_obj)
181
+ end
182
+
183
+ def self.authentication_error(error, rcode, rbody, error_obj)
184
+ AuthenticationError.new(error[:message], rcode, rbody, error_obj)
185
+ end
186
+
187
+ def self.permission_error(error, rcode, rbody, error_obj)
188
+ PermissionError.new(error[:message], error[:param], error[:code], rcode, rbody, error_obj)
189
+ end
190
+
191
+ def self.api_error(error, rcode, rbody, error_obj)
192
+ APIError.new(error[:message], rcode, rbody, error_obj)
193
+ end
194
+
195
+ def self.handle_restclient_error(e)
196
+ case e
197
+ when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
198
+ message = "Could not connect to Postmaster ($apiBase). Please check your internet connection and try again. If this problem persists, let us know at support@postmaster.io."
199
+ when RestClient::SSLCertificateNotVerified
200
+ message = "Could not verify Postmaster's SSL certificate. If this problem persists, let us know at support@postmaster.io."
201
+ else
202
+ message = "Unexpected error communicating with Postmaster. If this problem persists, let us know at support@postmaster.io."
203
+ end
204
+ message += "\n\n(Network error: #{e.message})"
205
+ raise APIConnectionError.new(message)
206
+ end
207
+ end
208
+
@@ -0,0 +1,17 @@
1
+ module Postmaster
2
+
3
+ class Address < APIResource
4
+ end
5
+
6
+ class AddressProposal < APIResource
7
+ end
8
+
9
+ class AddressValidation < APIResource
10
+
11
+ def self.validate(params={})
12
+ response = Postmaster.request(:post, '/v1/validate', params)
13
+ self.construct_from(response)
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module Postmaster
2
+ module APIOperations
3
+ module Create
4
+ module ClassMethods
5
+ def create(params={})
6
+ response = Postmaster.request(:post, self.url, params)
7
+ self.construct_from(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,16 @@
1
+ module Postmaster
2
+ module APIOperations
3
+ module List
4
+ module ClassMethods
5
+ def all(filters={})
6
+ response = Postmaster.request(:get, url, filters)
7
+ response[:results].map { |i| self.construct_from(i) }
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,36 @@
1
+ module Postmaster
2
+ class APIResource < PostmasterObject
3
+ def self.class_name
4
+ self.name.split('::')[-1]
5
+ end
6
+
7
+ def self.url()
8
+ if self == APIResource
9
+ raise NotImplementedError.new('APIResource is an abstract class. You should perform actions on its subclasses (Charge, Customer, etc.)')
10
+ end
11
+ "/v1/#{CGI.escape(class_name.downcase)}s"
12
+ end
13
+
14
+ def url(action=nil)
15
+ unless id = self['id']
16
+ raise InvalidRequestError.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{id.inspect}", 'id')
17
+ end
18
+ if action.nil?
19
+ return "#{self.class.url}/#{CGI.escape(id.to_s)}"
20
+ end
21
+ "#{self.class.url}/#{CGI.escape(id.to_s)}/#{CGI.escape(action)}"
22
+ end
23
+
24
+ def refresh
25
+ response = Postmaster.request(:get, url)
26
+ refresh_from(response)
27
+ self
28
+ end
29
+
30
+ def self.retrieve(id)
31
+ instance = self.new(id)
32
+ instance.refresh
33
+ instance
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ module Postmaster
2
+ class APIConnectionError < PostmasterError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Postmaster
2
+ class APIError < PostmasterError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Postmaster
2
+ class AuthenticationError < PostmasterError
3
+ end
4
+ end
@@ -0,0 +1,10 @@
1
+ module Postmaster
2
+ class InvalidRequestError < PostmasterError
3
+ attr_accessor :param
4
+
5
+ def initialize(message, param, http_status=nil, http_body=nil, json_body=nil)
6
+ super(message, http_status, http_body, json_body)
7
+ @param = param
8
+ end
9
+ end
10
+ end