easypost 1.1.3 → 2.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.
- data/.gitignore +27 -0
- data/CHANGELOG +0 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +30 -0
- data/LICENSE +21 -0
- data/README.md +99 -0
- data/Rakefile +0 -0
- data/VERSION +1 -0
- data/bin/easypost-irb +7 -0
- data/easypost.gemspec +24 -0
- data/lib/easypost.rb +105 -34
- data/lib/easypost/address.rb +14 -7
- data/lib/easypost/batch.rb +34 -0
- data/lib/easypost/customs_info.rb +4 -0
- data/lib/easypost/customs_item.rb +4 -0
- data/lib/easypost/{errors/easypost_error.rb → error.rb} +10 -1
- data/lib/easypost/object.rb +120 -0
- data/lib/easypost/parcel.rb +4 -0
- data/lib/easypost/postage_label.rb +4 -0
- data/lib/easypost/rate.rb +4 -0
- data/lib/easypost/refund.rb +4 -0
- data/lib/easypost/resource.rb +67 -0
- data/lib/easypost/scan_form.rb +4 -0
- data/lib/easypost/shipment.rb +82 -0
- data/lib/easypost/util.rb +102 -0
- data/lib/easypost/version.rb +3 -0
- data/spec/address_spec.rb +75 -0
- data/spec/batch_spec.rb +96 -0
- data/spec/refund_spec.rb +65 -0
- data/spec/scan_form_spec.rb +63 -0
- data/spec/shipment_spec.rb +141 -0
- data/spec/spec_helper.rb +84 -0
- metadata +66 -15
- data/lib/easypost/errors/authentication_error.rb +0 -4
- data/lib/easypost/postage.rb +0 -52
@@ -1,15 +1,24 @@
|
|
1
1
|
module EasyPost
|
2
|
-
class
|
2
|
+
class Error < StandardError
|
3
3
|
attr_reader :message
|
4
4
|
attr_reader :http_status
|
5
5
|
attr_reader :http_body
|
6
6
|
attr_reader :json_body
|
7
|
+
attr_reader :param
|
7
8
|
|
8
9
|
def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
|
9
10
|
@message = message
|
10
11
|
@http_status = http_status
|
11
12
|
@http_body = http_body
|
12
13
|
@json_body = json_body
|
14
|
+
|
15
|
+
begin
|
16
|
+
@param = @json_body[:error][:param]
|
17
|
+
rescue
|
18
|
+
@param = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
super(message)
|
13
22
|
end
|
14
23
|
|
15
24
|
def to_s
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module EasyPost
|
2
|
+
class EasyPostObject
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_accessor :api_key
|
6
|
+
@@immutable_values = Set.new([:api_key, :id])
|
7
|
+
|
8
|
+
def initialize(id=nil, api_key=nil)
|
9
|
+
@api_key = api_key
|
10
|
+
@values = {}
|
11
|
+
@unsaved_values = Set.new
|
12
|
+
@transient_values = Set.new
|
13
|
+
self.id = id if id
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.construct_from(values, api_key=nil)
|
17
|
+
obj = self.new(values[:id], api_key)
|
18
|
+
obj.refresh_from(values, api_key)
|
19
|
+
obj
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s(*args)
|
23
|
+
MultiJson.dump(@values, :pretty => true)
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect()
|
27
|
+
id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
|
28
|
+
"#<#{self.class}:#{id_string}> JSON: " + MultiJson.dump(@values, :pretty => true)
|
29
|
+
end
|
30
|
+
|
31
|
+
def refresh_from(values, api_key, partial=false)
|
32
|
+
@api_key = api_key
|
33
|
+
|
34
|
+
added = Set.new(values.keys - @values.keys)
|
35
|
+
|
36
|
+
instance_eval do
|
37
|
+
add_accessors(added)
|
38
|
+
end
|
39
|
+
|
40
|
+
values.each do |k, v|
|
41
|
+
@values[k] = Util.convert_to_easypost_object(v, api_key)
|
42
|
+
@transient_values.delete(k)
|
43
|
+
@unsaved_values.delete(k)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](k)
|
48
|
+
k = k.to_sym if k.is_a?(String)
|
49
|
+
@values[k]
|
50
|
+
end
|
51
|
+
|
52
|
+
def []=(k, v)
|
53
|
+
send(:"#{k}=", v)
|
54
|
+
end
|
55
|
+
|
56
|
+
def keys
|
57
|
+
@values.keys
|
58
|
+
end
|
59
|
+
|
60
|
+
def values
|
61
|
+
@values.values
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_json(options = {})
|
65
|
+
MultiJson.dump(@values)
|
66
|
+
end
|
67
|
+
|
68
|
+
def as_json(options = {})
|
69
|
+
@values.as_json
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_hash
|
73
|
+
@values
|
74
|
+
end
|
75
|
+
|
76
|
+
def each(&blk)
|
77
|
+
@values.each(&blk)
|
78
|
+
end
|
79
|
+
|
80
|
+
def id=(id)
|
81
|
+
@values[:id] = id
|
82
|
+
end
|
83
|
+
|
84
|
+
def id
|
85
|
+
@values[:id]
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def metaclass
|
91
|
+
class << self; self; end
|
92
|
+
end
|
93
|
+
|
94
|
+
def remove_accessors(keys)
|
95
|
+
metaclass.instance_eval do
|
96
|
+
keys.each do |k|
|
97
|
+
next if @@immutable_values.include?(k)
|
98
|
+
k_eq = :"#{k}="
|
99
|
+
remove_method(k) if method_defined?(k)
|
100
|
+
remove_method(k_eq) if method_defined?(k_eq)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_accessors(keys)
|
106
|
+
metaclass.instance_eval do
|
107
|
+
keys.each do |k|
|
108
|
+
next if @@immutable_values.include?(k)
|
109
|
+
k_eq = :"#{k}="
|
110
|
+
define_method(k) { @values[k] }
|
111
|
+
define_method(k_eq) do |v|
|
112
|
+
@values[k] = v
|
113
|
+
@unsaved_values.add(k)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module EasyPost
|
2
|
+
class Resource < EasyPostObject
|
3
|
+
def self.class_name
|
4
|
+
camel = self.name.split('::')[-1]
|
5
|
+
snake = camel[0..0] + camel[1..-1].gsub(/([A-Z])/, '_\1')
|
6
|
+
return snake.downcase
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.url
|
10
|
+
if self.class_name == 'resource'
|
11
|
+
raise NotImplementedError.new('Resource is an abstract class. You should perform actions on its subclasses (Address, Shipment, etc.)')
|
12
|
+
end
|
13
|
+
if(self.class_name[-1..-1] == 's' || self.class_name[-1..-1] == 'h')
|
14
|
+
return "/#{CGI.escape(self.class_name.downcase)}es"
|
15
|
+
else
|
16
|
+
return "/#{CGI.escape(class_name.downcase)}s"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def url
|
21
|
+
unless self.id
|
22
|
+
raise Error.new("Could not determine which URL to request: #{self.class} instance has invalid ID: #{self.id.inspect}")
|
23
|
+
end
|
24
|
+
return "#{self.class.url}/#{CGI.escape(id)}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def refresh
|
28
|
+
response, api_key = EasyPost.request(:get, url, @api_key, @retrieve_options)
|
29
|
+
refresh_from(response, api_key)
|
30
|
+
return self
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.all(filters={}, api_key=nil)
|
34
|
+
response, api_key = EasyPost.request(:get, url, api_key, filters)
|
35
|
+
return Util.convert_to_easypost_object(response, api_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.retrieve(id, api_key=nil)
|
39
|
+
instance = self.new(id, api_key)
|
40
|
+
instance.refresh
|
41
|
+
return instance
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.create(params={}, api_key=nil)
|
45
|
+
wrapped_params = {}
|
46
|
+
wrapped_params[self.class_name().to_sym] = params
|
47
|
+
response, api_key = EasyPost.request(:post, self.url, api_key, wrapped_params)
|
48
|
+
return Util.convert_to_easypost_object(response, api_key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete
|
52
|
+
response, api_key = EasyPost.request(:delete, url, @api_key)
|
53
|
+
refresh_from(response, api_key)
|
54
|
+
return self
|
55
|
+
end
|
56
|
+
|
57
|
+
def save
|
58
|
+
if @unsaved_values.length > 0
|
59
|
+
values = {}
|
60
|
+
@unsaved_values.each { |k| values[k] = @values[k] }
|
61
|
+
response, api_key = EasyPost.request(:post, url, @api_key, values)
|
62
|
+
refresh_from(response, api_key)
|
63
|
+
end
|
64
|
+
return self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module EasyPost
|
2
|
+
class Shipment < Resource
|
3
|
+
|
4
|
+
def get_rates(params={})
|
5
|
+
response, api_key = EasyPost.request(:get, url + '/rates', @api_key, params)
|
6
|
+
self.refresh_from(response, @api_key, true)
|
7
|
+
|
8
|
+
return self
|
9
|
+
end
|
10
|
+
|
11
|
+
def buy(params={})
|
12
|
+
response, api_key = EasyPost.request(:post, url + '/buy', @api_key, params)
|
13
|
+
self.refresh_from(response, @api_key, true)
|
14
|
+
|
15
|
+
return self
|
16
|
+
end
|
17
|
+
|
18
|
+
def refund(params={})
|
19
|
+
response, api_key = EasyPost.request(:get, url + '/refund', @api_key, params)
|
20
|
+
self.refresh_from(response, @api_key, true)
|
21
|
+
|
22
|
+
return self
|
23
|
+
end
|
24
|
+
|
25
|
+
def track(params={})
|
26
|
+
response, api_key = EasyPost.request(:get, url + '/track', @api_key, params)
|
27
|
+
self.refresh_from(response, @api_key, true)
|
28
|
+
|
29
|
+
return self
|
30
|
+
end
|
31
|
+
|
32
|
+
def stamp(params={})
|
33
|
+
response, api_key = EasyPost.request(:get, url + '/stamp', @api_key, params)
|
34
|
+
|
35
|
+
return response[:stamp_url]
|
36
|
+
end
|
37
|
+
|
38
|
+
def barcode(params={})
|
39
|
+
response, api_key = EasyPost.request(:get, url + '/barcode', @api_key, params)
|
40
|
+
|
41
|
+
return response[:barcode_url]
|
42
|
+
end
|
43
|
+
|
44
|
+
def lowest_rate(carriers=[], services=[])
|
45
|
+
lowest = nil
|
46
|
+
|
47
|
+
self.get_rates unless self.rates
|
48
|
+
|
49
|
+
if !carriers.is_a?(Array)
|
50
|
+
carriers = carriers.split(',')
|
51
|
+
end
|
52
|
+
carriers.map!(&:downcase)
|
53
|
+
|
54
|
+
if !services.is_a?(Array)
|
55
|
+
services = services.split(',')
|
56
|
+
end
|
57
|
+
services.map!(&:downcase)
|
58
|
+
|
59
|
+
self.rates.each do |k|
|
60
|
+
|
61
|
+
rate_carrier = k.carrier.downcase
|
62
|
+
if carriers.size() > 0 && !carriers.include?(rate_carrier)
|
63
|
+
next
|
64
|
+
end
|
65
|
+
|
66
|
+
rate_service = k.service.downcase
|
67
|
+
if services.size() > 0 && !services.include?(rate_service)
|
68
|
+
next
|
69
|
+
end
|
70
|
+
|
71
|
+
if lowest == nil || k.rate.to_f < lowest.rate.to_f
|
72
|
+
lowest = k
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
raise Error.new('No rates found.') if lowest == nil
|
77
|
+
|
78
|
+
return lowest
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module EasyPost
|
2
|
+
module Util
|
3
|
+
def self.objects_to_ids(obj)
|
4
|
+
case obj
|
5
|
+
when Resource
|
6
|
+
return {:id => obj.id}
|
7
|
+
when Hash
|
8
|
+
result = {}
|
9
|
+
obj.each { |k, v| result[k] = objects_to_ids(v) unless v.nil? }
|
10
|
+
return result
|
11
|
+
when Array
|
12
|
+
return obj.map { |v| objects_to_ids(v) }
|
13
|
+
else
|
14
|
+
return obj
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.convert_to_easypost_object(response, api_key)
|
19
|
+
types = { 'Address' => Address,
|
20
|
+
'ScanForm' => ScanForm,
|
21
|
+
'CustomsItem' => CustomsItem,
|
22
|
+
'CustomsInfo' => CustomsInfo,
|
23
|
+
'Parcel' => Parcel,
|
24
|
+
'Shipment' => Shipment,
|
25
|
+
'Rate' => Rate,
|
26
|
+
'Refund' => Refund,
|
27
|
+
'Batch' => Batch,
|
28
|
+
'PostageLabel' => PostageLabel }
|
29
|
+
|
30
|
+
prefixes = { 'adr' => Address,
|
31
|
+
'sf' => ScanForm,
|
32
|
+
'cstitem' => CustomsItem,
|
33
|
+
'cstinfo' => CustomsInfo,
|
34
|
+
'prcl' => Parcel,
|
35
|
+
'shp' => Shipment,
|
36
|
+
'rate' => Rate,
|
37
|
+
'rfnd' => Refund,
|
38
|
+
'batch' => Batch,
|
39
|
+
'pl' => PostageLabel }
|
40
|
+
|
41
|
+
case response
|
42
|
+
when Array
|
43
|
+
return response.map { |i| convert_to_easypost_object(i, api_key) }
|
44
|
+
when Hash
|
45
|
+
if cls_name = response[:object]
|
46
|
+
cls = types[cls_name]
|
47
|
+
elsif response[:id] && cls_prefix = response[:id][0..response[:id].index('_')]
|
48
|
+
cls = prefixes[cls_prefix[0..-2]]
|
49
|
+
end
|
50
|
+
|
51
|
+
cls ||= EasyPostObject
|
52
|
+
return cls.construct_from(response, api_key)
|
53
|
+
else
|
54
|
+
return response
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.symbolize_names(obj)
|
59
|
+
case obj
|
60
|
+
when Hash
|
61
|
+
result = {}
|
62
|
+
obj.each do |k, v|
|
63
|
+
k = (k.to_sym rescue k) || k
|
64
|
+
obj[k] = symbolize_names(v)
|
65
|
+
end
|
66
|
+
return result
|
67
|
+
when Array
|
68
|
+
return obj.map { |v| symbolize_names(v) }
|
69
|
+
else
|
70
|
+
return obj
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.url_encode(key)
|
75
|
+
URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.flatten_params(params, parent_key=nil)
|
79
|
+
result = []
|
80
|
+
if params.is_a?(Hash)
|
81
|
+
params.each do |k, v|
|
82
|
+
calculated_key = parent_key ? "#{parent_key}[#{url_encode(k)}]" : url_encode(k)
|
83
|
+
if v.is_a?(Hash) or v.is_a?(Array)
|
84
|
+
result += flatten_params(v, calculated_key)
|
85
|
+
else
|
86
|
+
result << [calculated_key, v]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
elsif params.is_a?(Array)
|
90
|
+
params.each_with_index do |v, i|
|
91
|
+
calculated_key = parent_key ? "#{parent_key}[#{i}]" : i
|
92
|
+
if v.is_a?(Hash) or v.is_a?(Array)
|
93
|
+
result += flatten_params(v, calculated_key)
|
94
|
+
else
|
95
|
+
result << [calculated_key, v]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
return result
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|