shippo 1.0.4 → 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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.atom-build.json +22 -0
  3. data/.codeclimate.yml +30 -0
  4. data/.gitignore +22 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +1156 -0
  7. data/.travis.yml +16 -0
  8. data/CHANGELOG.md +50 -0
  9. data/Gemfile +5 -0
  10. data/Guardfile +39 -0
  11. data/README.md +288 -0
  12. data/Rakefile +24 -0
  13. data/bin/example +114 -0
  14. data/lib/shippo.rb +23 -97
  15. data/lib/shippo/api.rb +52 -0
  16. data/lib/shippo/api/api_object.rb +133 -0
  17. data/lib/shippo/api/category.rb +49 -0
  18. data/lib/shippo/api/category/base.rb +144 -0
  19. data/lib/shippo/api/category/purpose.rb +13 -0
  20. data/lib/shippo/api/category/source.rb +16 -0
  21. data/lib/shippo/api/category/state.rb +13 -0
  22. data/lib/shippo/api/category/status.rb +17 -0
  23. data/lib/shippo/api/extend/operation.rb +21 -0
  24. data/lib/shippo/api/extend/transformers.rb +12 -0
  25. data/lib/shippo/api/extend/url.rb +26 -0
  26. data/lib/shippo/api/operations.rb +8 -0
  27. data/lib/shippo/api/operations/create.rb +33 -0
  28. data/lib/shippo/api/operations/list.rb +22 -0
  29. data/lib/shippo/api/operations/rates.rb +16 -0
  30. data/lib/shippo/api/operations/update.rb +12 -0
  31. data/lib/shippo/api/operations/validate.rb +12 -0
  32. data/lib/shippo/api/request.rb +159 -0
  33. data/lib/shippo/api/resource.rb +104 -0
  34. data/lib/shippo/api/transformers/list.rb +73 -0
  35. data/lib/shippo/api/version.rb +5 -0
  36. data/lib/shippo/exceptions.rb +7 -0
  37. data/lib/shippo/exceptions/api_error.rb +20 -0
  38. data/lib/shippo/exceptions/error.rb +22 -0
  39. data/lib/shippo/model/address.rb +5 -0
  40. data/lib/shippo/model/carrieraccount.rb +5 -0
  41. data/lib/shippo/model/customs_declaration.rb +6 -0
  42. data/lib/shippo/model/customs_item.rb +5 -0
  43. data/lib/shippo/model/manifest.rb +5 -0
  44. data/lib/shippo/model/parcel.rb +5 -0
  45. data/lib/shippo/model/rate.rb +5 -0
  46. data/lib/shippo/model/refund.rb +5 -0
  47. data/lib/shippo/model/shipment.rb +5 -0
  48. data/lib/shippo/model/transaction.rb +5 -0
  49. data/lib/shippo/tasks/shippo.rb +22 -0
  50. data/shippo.gemspec +34 -0
  51. metadata +226 -40
  52. data/example.rb +0 -71
  53. data/lib/shippo/address.rb +0 -10
  54. data/lib/shippo/api_object.rb +0 -89
  55. data/lib/shippo/carrieraccount.rb +0 -8
  56. data/lib/shippo/container_object.rb +0 -28
  57. data/lib/shippo/create.rb +0 -18
  58. data/lib/shippo/customs_declaration.rb +0 -7
  59. data/lib/shippo/customs_item.rb +0 -7
  60. data/lib/shippo/error.rb +0 -18
  61. data/lib/shippo/list.rb +0 -23
  62. data/lib/shippo/manifest.rb +0 -6
  63. data/lib/shippo/parcel.rb +0 -6
  64. data/lib/shippo/rate.rb +0 -5
  65. data/lib/shippo/refund.rb +0 -6
  66. data/lib/shippo/resource.rb +0 -29
  67. data/lib/shippo/shipment.rb +0 -14
  68. data/lib/shippo/transaction.rb +0 -6
  69. data/lib/shippo/update.rb +0 -15
  70. data/test/test.rb +0 -26
@@ -0,0 +1,13 @@
1
+ require_relative 'base'
2
+ module Shippo
3
+ module API
4
+ module Category
5
+ # Indicates whether a shipment can be used to purchase Labels or only to obtain quote Rates.
6
+ # Note that if at least one quote Address is passed in the original request,
7
+ # the Shipment will be eligible for quotes only.
8
+ class Purpose < Base
9
+ allowed_values :purchase, :quote
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'base'
2
+ module Shippo
3
+ module API
4
+ module Category
5
+ #
6
+ # +Source+ category describes the origin of an address in the following way:
7
+ # * +:fully_entered+ addresses only contain user-given values and are eligible for purchasing a label.
8
+ # * +:partially_entered+ addresses lack some required information and only qualify for requesting rates.
9
+ # * +:validator+ addresses have been created by the address validation service.
10
+ #
11
+ class Source < Base
12
+ allowed_values :fully_entered, :partially_entered, :validator
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'base'
2
+ module Shippo
3
+ module API
4
+ module Category
5
+ # "VALID" shipments contain all required values and can be used to get rates and purchase labels.
6
+ # "INCOMPLETE" shipments lack required values but can be used for getting rates.
7
+ # "INVALID" shipments can't be used for getting rates or labels.
8
+ class State < Base
9
+ allowed_values :valid, :invalid, :incomplete
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'base'
2
+ module Shippo
3
+ module API
4
+ module Category
5
+ # +Status+ is a category class with the following possible values:
6
+ #
7
+ # * "Waiting" shipments have been successfully submitted but not yet been processed.
8
+ # * "Queued" shipments are currently being processed.
9
+ # * "Success" shipments have been processed successfully,
10
+ # meaning that rate generation has concluded.
11
+ # * "Error" does not occur currently and is reserved for future use.
12
+ class Status < Base
13
+ allowed_values :waiting, :queued, :success, :error
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module Shippo
2
+ module API
3
+ module Extend
4
+ module Operation
5
+ def self.included(klass)
6
+ klass.instance_eval do
7
+ class << self
8
+ def operations(*ops)
9
+ ops.each do |operation|
10
+ module_name = "Shippo::API::Operations::#{operation.to_s.capitalize}"
11
+ # noinspection RubyResolve
12
+ self.extend(module_name.constantize)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ require_relative '../transformers/list.rb'
2
+ module Shippo
3
+ module API
4
+ module Extend
5
+ module Transformers
6
+ def transformers
7
+ @transformers ||= [ Shippo::API::Transformers::List ].freeze
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ module Shippo
2
+ module API
3
+ module Extend
4
+ module Url
5
+ def self.included(klass)
6
+ klass.instance_eval do
7
+ @url = nil
8
+ class << self
9
+ # It's a getter and a class-level setter
10
+ def url(value = nil)
11
+ return @url if @url
12
+ @url ||= value if value
13
+ @url ||= class_to_url
14
+ end
15
+
16
+ def class_to_url
17
+ words = self.short_name.underscore.split(/_/)
18
+ words.map { |w| "/#{w == words.last ? w.pluralize : w}" }.join
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ module Shippo
2
+ module API
3
+ module Operations
4
+ end
5
+ end
6
+ end
7
+
8
+ Shippo.dir('shippo/api/operations')
@@ -0,0 +1,33 @@
1
+ require 'colored2'
2
+ require 'awesome_print'
3
+ module Shippo
4
+ module API
5
+ module Operations
6
+ module Create
7
+ # Creates an item in the database
8
+ # @param [Hash] params tacked onto the URL as URI parameters
9
+ def create(params={})
10
+ api_params = params.dup
11
+ Hashie::Extensions::StringifyKeys.stringify_keys!(api_params)
12
+
13
+ api_params.dup.each { |k, v| api_params[k] = v.id if v.is_a?(::Shippo::API::Resource) && v.id }
14
+
15
+ response = Shippo::API.post("#{url}/", api_params)
16
+ instance = self.from(response)
17
+
18
+ debug_log!(api_params, response, instance) if Shippo::API.debug?
19
+ instance
20
+ end
21
+
22
+ def debug_log!(api_params, response, instance)
23
+ puts "#{self.name}->create / request : \n".bold.green.underlined
24
+ ap(api_params)
25
+ puts "#{self.name}->create / response: \n".bold.yellow.underlined
26
+ ap(response)
27
+ puts "#{self.name}->create / from: \n".bold.blue.underlined
28
+ ap(instance)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ module Shippo
2
+ module API
3
+ module Operations
4
+ module List
5
+ # Return all items
6
+ # @param [Hash] params of additional URI parameters tacked onto the query URL
7
+ def all(params={})
8
+ response = Shippo::API.get("#{url}/", params)
9
+ self.from(response)
10
+ end
11
+
12
+ # Retrieve a concrete item by it's ID
13
+ # @param [Fixnum] id database ID of the item to be retrieved
14
+ # @param [Hash] params of additional URI parameters tacked onto the query URL
15
+ def get(id, params={})
16
+ response = Shippo::API.get("#{url}/#{CGI.escape(id)}/", params)
17
+ self.from(response)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ module Shippo
2
+ module API
3
+ module Operations
4
+ module Rates
5
+ def rates(currency = nil, params = {})
6
+ if !currency.nil?
7
+ response = Shippo::API.get("#{url}/rates/#{currency}/", params)
8
+ else
9
+ response = Shippo::API.get("#{url}/rates/", params)
10
+ end
11
+ Shippo::Rate.from(response[:results])
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ module Shippo
2
+ module API
3
+ module Operations
4
+ module Update
5
+ def update(object_id, params={})
6
+ response = Shippo::API.put("#{url}/#{CGI.escape(object_id)}/", params)
7
+ self.from(response)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Shippo
2
+ module API
3
+ module Operations
4
+ module Validate
5
+ def validate(params={})
6
+ response = Shippo::API.get("#{url}/validate/", params)
7
+ Shippo::Address.from(response)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,159 @@
1
+ require 'rest_client'
2
+ require 'socket'
3
+ require 'json'
4
+ require 'set'
5
+
6
+ require 'shippo/exceptions'
7
+
8
+ module Shippo
9
+ module API
10
+ #
11
+ # This class is the primary *internal* Interface to the Shippo API.
12
+ #
13
+ # Public consumers should use Model API, and perform actions on models
14
+ # rather than submit requests directly using this class.
15
+ #
16
+ # +Request+ instance is created with the intention to execute a single
17
+ # API call and once executed, it stores +response+ object. Used
18
+ # requests can not be re-executed.
19
+ #
20
+ # == Example
21
+ #
22
+ # @request = Shippo::API::Request.new(
23
+ # method: :get,
24
+ # uri: '/address,
25
+ # params: { object_id: 1 },
26
+ # headers: { 'Last-Modified' => '1213145' }
27
+ # begin
28
+ # @response = @request.execute
29
+ # Shippo::Address.from(@response)
30
+ # # =>
31
+ #
32
+ class Request
33
+ attr_accessor :username, :password
34
+ attr_accessor :method, :url, :params, :headers
35
+
36
+ # Result of the execute method is stored in #response and #parsed_response
37
+ attr_accessor :response, :parsed_response
38
+
39
+ # @param [symbol] method :get or any other method such as :put, :post, etc.
40
+ # @param [String] uri URI component appended to the base URL
41
+ # @param [Hash] params parameters to append to the URL
42
+ # @param [Hash] headers headers hash sent to the server
43
+ def initialize(method:, uri:, params: {}, headers: {})
44
+ self.method = method
45
+ self.params = params
46
+ self.headers = headers
47
+ self.url = api_url(uri)
48
+ self.response = nil
49
+ end
50
+
51
+ def execute
52
+ raise ArgumentError.new('Response is already defined, create another Request object.') if self.response
53
+ validate!
54
+ begin
55
+ self.response = shippo_phone_home
56
+ self.parsed_response = JSON::parse(response.body, { symbolize_names: true })
57
+
58
+ rescue ::RestClient::Unauthorized => e
59
+ raise Shippo::Exceptions::AuthenticationError.new(e.message)
60
+
61
+ rescue ::JSON::JSONError, ::JSON::ParserError, ::RestClient::BadRequest
62
+ raise Shippo::Exceptions::APIServerError.new('Unable to read data received back from the server', self)
63
+
64
+ rescue ::RestClient::Exception => e
65
+ raise Shippo::Exceptions::ConnectionError.new(connection_error_message(url, e))
66
+
67
+ rescue StandardError => e
68
+ raise Shippo::Exceptions::ConnectionError.new(connection_error_message(url, e)) if e.message =~ /TCP|connection|getaddrinfo/
69
+
70
+ STDERR.puts "#{self.class.name}: Internal error occurred while connecting to #{url}: #{e.message}".bold.red
71
+ STDERR.puts 'Stack Trace'.bold.yellow.underlined
72
+ STDERR.puts e.backtrace.join("\n").yellow
73
+ raise Shippo::Exceptions::Error.new(e)
74
+ end
75
+ self.parsed_response
76
+ end
77
+
78
+ private
79
+
80
+ def shippo_phone_home
81
+ payload = {}
82
+ request_url = url
83
+ (method == :get) ? request_url = params_to_url(params, url) : payload = params.to_json
84
+ setup_headers!(headers)
85
+ opts = make_opts!(headers, method, payload, request_url)
86
+ make_request!(opts)
87
+ end
88
+
89
+ def make_request!(opts)
90
+ RestClient::Request.execute(opts) { |response, request, result, &block|
91
+ if [301, 302, 307].include? response.code
92
+ response.follow_redirection(request, result, &block)
93
+ else
94
+ response.return!(request, result, &block)
95
+ end
96
+ }
97
+ end
98
+
99
+ def make_opts!(headers, method, payload, url)
100
+ { :headers => headers,
101
+ :method => method,
102
+ :payload => payload,
103
+ :url => url,
104
+ :open_timeout => 15,
105
+ :timeout => 30,
106
+ :user => username,
107
+ :password => password,
108
+ :user_agent => 'Shippo/v2.0 RubyBindings'
109
+ }
110
+ end
111
+
112
+ def setup_headers!(headers)
113
+ headers.merge!(
114
+ :accept => :json,
115
+ :content_type => :json,
116
+ :Authorization => "ShippoToken #{token}"
117
+ )
118
+ end
119
+
120
+ def base
121
+ ::Shippo::API.base
122
+ end
123
+
124
+ def token
125
+ ::Shippo::API.token
126
+ end
127
+
128
+ def connection_error_message(url, error)
129
+ %Q[Could not connect to the Shippo API, via URL
130
+ #{url}.
131
+
132
+ Please check your Internet connection, try again, if the problem
133
+ persists please contact Shippo Customer Support.
134
+
135
+ Error Description:
136
+ #{error.class.name} ⇨ #{error.message}].gsub(/^\s*/, '')
137
+ end
138
+
139
+ def api_url(uri_component = '')
140
+ base + uri_component
141
+ end
142
+
143
+ def params_to_url(params, url)
144
+ pairs = []
145
+ params.each { |k, v|
146
+ pairs.push "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
147
+ }
148
+ url += "?#{pairs.join('&')}" unless pairs.empty?
149
+ url
150
+ end
151
+
152
+ def validate!
153
+ raise Shippo::Exceptions::AuthenticationError.new(
154
+ 'API credentials seems to be missing, perhaps you forgot to set Shippo::API.token?') \
155
+ unless token
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,104 @@
1
+ require 'forwardable'
2
+
3
+ require 'hashie/mash'
4
+ require 'active_support/inflector'
5
+
6
+ require 'shippo/exceptions'
7
+
8
+ require_relative 'api_object'
9
+ require_relative 'category/status'
10
+ require_relative 'transformers/list'
11
+ require_relative 'extend/operation'
12
+ require_relative 'extend/transformers'
13
+ require_relative 'extend/url'
14
+
15
+ module Shippo
16
+ module API
17
+ class Resource < Hashie::Mash
18
+ include Hashie::Extensions::StringifyKeys
19
+ include Enumerable
20
+ extend Forwardable
21
+
22
+ def self.object_properties
23
+ Shippo::API::ApiObject::PROPS
24
+ end
25
+
26
+ attr_accessor :object
27
+ def_delegators :@object, *object_properties
28
+
29
+ # Creates a possibly recursive chain (map of lists, etc) of Resource
30
+ # instances based on whether each value is a scalar, array or a hash.
31
+ def self.from(values)
32
+ # recursive on arrays
33
+ if values.is_a?(Array)
34
+ values.map { |list| from(list) }
35
+ elsif values.respond_to?(:keys) # a Hash or a Hash derivative
36
+ new(values)
37
+ else
38
+ values
39
+ end
40
+ end
41
+
42
+ def self.short_name(name = self.name)
43
+ name.split('::')[-1]
44
+ end
45
+
46
+ # Generate object_ accessors.
47
+ object_properties.each do |property|
48
+ method_name = ApiObject.field_name(property)
49
+ define_method method_name do
50
+ STDOUT.puts "#{method_name} style accessors are deprecated in favor of #resource.object.#{property}" if Shippo::API.warnings
51
+ self.object.send(property)
52
+ end
53
+ end
54
+
55
+ # allows resources to use default or a custom url
56
+ include Shippo::API::Extend::Url
57
+ # allows resources to set supported operations
58
+ include Shippo::API::Extend::Operation
59
+ # adds #transformers method that enumerates available transformers
60
+ # of the hashes into other types.
61
+ include Shippo::API::Extend::Transformers
62
+
63
+ # As a Hashie::Mash subclass, Resource can initialize from another hash
64
+ def initialize(*args)
65
+ if args.first.is_a?(Fixnum) or
66
+ (args.first.is_a?(String) && args.first =~ /^[0-9A-Fa-f]+$/)
67
+ self.id = args.first
68
+ elsif args.first.respond_to?(:keys)
69
+ h = Hashie::Mash.new(args.first)
70
+ self.deep_merge!(h)
71
+ self.object = ApiObject.create_object(self)
72
+ transformers.each do |transformer|
73
+ transformer.new(self).transform
74
+ end
75
+ else
76
+ super(*args)
77
+ end
78
+ end
79
+
80
+ def inspect
81
+ "#<#{self.class.short_name}:0x#{self.object_id.to_s(16)}#{id.nil? ? '' : "[id=#{id}]"}#{to_hash.inspect}->#{object.inspect}"
82
+ end
83
+
84
+ def to_s
85
+ self.class.short_name + self.to_hash.to_s + '->' + self.object.to_s
86
+ end
87
+
88
+ def url
89
+ raise Shippo::Exceptions::MissingDataError.new("#{self.class} has no object_id") unless id
90
+ "#{self.class.url}/#{CGI.escape(id)}"
91
+ end
92
+
93
+ def refresh
94
+ response = Shippo::API.get(url)
95
+ self.from(response)
96
+ self
97
+ end
98
+
99
+ def success?
100
+ self.object && self.object.status && self.object.status.eql?(Shippo::API::Category::Status::SUCCESS)
101
+ end
102
+ end
103
+ end
104
+ end