postmaster 1.0.0

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.
@@ -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