postmen 1.0.0.pre.alpha.1 → 1.0.0.pre.alpha.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ef3279747f60ddf021d56412eed1d833adb77f1c
4
- data.tar.gz: e93b892733da84d2b7d3ae556c004baef30bc9f6
3
+ metadata.gz: 05652c1c08402bef17419ea6b02586580eae907e
4
+ data.tar.gz: 8f6676faa166b9ba2f59248593198cfc4d60e8c0
5
5
  SHA512:
6
- metadata.gz: 721df92fa7baf87693a78098a04cf5f1986f8829ff2f83a752899767fd2aa147ab252f16065b807408108e3872ae6e81c972c024c7231c06ea6c09ec4a02a625
7
- data.tar.gz: 9436a466ca9044c098e05e3df986861bbe5f64dd257f944f486341cddc5dfbd791437bc0dee7ffea3df7e234f352fd96f38893dd0380dbd7ced2ab4f7f5867db
6
+ metadata.gz: 47156b956f5511b485afbee474f381f78e080d7844d642e7d39654417039cfc5b7bea409adef20ffc65df77d8d5bc1bcc5f31f1b04ab8f0b7d9230e30abdf9a0
7
+ data.tar.gz: bf6f08c3f8ace491be96fe8d75b90a82663386008a64ecb6ddd633ea7a1dcb5f3aa4ad9061e4dab3e3a91162cf8acc9bb37c548e7bc84127283133c3ee778ec8
@@ -5,6 +5,12 @@ class Postmen
5
5
  # Maximum number of retries
6
6
  MAX_REQUESTS = 5
7
7
 
8
+ # Main domain used during normal usage.
9
+ MAIN_DOMAIN = 'postmen.com'.freeze
10
+
11
+ # Failover domain used during DNS issues with main domain
12
+ FAILOVER_DOMAIN = 'postmen.net'.freeze
13
+
8
14
  def initialize
9
15
  @requests = 0
10
16
  end
@@ -17,12 +23,9 @@ class Postmen
17
23
  # .get('/labels')
18
24
  # .get('/labels', { params: { limit: 5 } })
19
25
  def get(path, options = {})
20
- Response.new(raw_get(path, options)).tap(&:parse_response!)
21
- rescue RateLimitExceeded
22
- @requests += 1
23
- raise if @requests > MAX_REQUESTS
24
- sleep(60)
25
- retry
26
+ with_error_handling do
27
+ Response.new(raw_get(path, options)).tap(&:parse_response!)
28
+ end
26
29
  end
27
30
 
28
31
  # Performs a HTTP POST request.
@@ -33,7 +36,9 @@ class Postmen
33
36
  # .post('/labels')
34
37
  # .post('/labels', { json: { my: { sample: :data } } })
35
38
  def post(path, options = {})
36
- Response.new(raw_post(path, options))
39
+ with_error_handling do
40
+ Response.new(raw_post(path, options))
41
+ end
37
42
  end
38
43
 
39
44
  # Performs a HTTP PUT request.
@@ -44,8 +49,11 @@ class Postmen
44
49
  # .put('/shipper-accounts/123/info')
45
50
  # ..put('/shipper-accounts/123/info', { json: { my: { sample: :data } } })
46
51
  def put(path, options = {})
47
- Response.new(raw_put(path, options))
52
+ with_error_handling do
53
+ Response.new(raw_put(path, options)).tap(&:parse_response!)
54
+ end
48
55
  end
56
+
49
57
  # Performs a HTTP DELETE request
50
58
  #
51
59
  # @param path [String]
@@ -57,8 +65,74 @@ class Postmen
57
65
  .delete(get_full_url(path))
58
66
  end
59
67
 
68
+ # Returns the endpoint used in SDK, based on the region(subdomain) and
69
+ # a failover switch
70
+ #
71
+ # @param subdomain [String] The region/subdomain used.
72
+ # @param failover [Boolean]
73
+ def self.endpoint(subdomain, failover = false)
74
+ URI::HTTPS.build(scheme: 'https',
75
+ host: hostname(subdomain, failover),
76
+ path: '/v3').to_s
77
+ end
78
+
79
+ # Returns the hostname based on the region(subdomain) and a failover switch
80
+ #
81
+ # @param subdomain [String] The region/subdomain used.
82
+ # @param failover [Boolean]
83
+ def self.hostname(subdomain, failover)
84
+ base = failover ? FAILOVER_DOMAIN : MAIN_DOMAIN
85
+ [subdomain, base].join('.')
86
+ end
87
+
60
88
  private
61
89
 
90
+ # rubocop:disable Metrics/MethodLength
91
+ # rubocop:disable Metrics/AbcSize
92
+ # rubocop:disable Metrics/CyclomaticComplexity
93
+ def with_error_handling
94
+ raise ArgumentError unless block_given?
95
+
96
+ begin
97
+ yield
98
+ # Rescue from any connection issues
99
+ rescue HTTP::ConnectionError
100
+ # Raise error if we already tried to use failover domain
101
+ raise if Postmen.failover?
102
+ # Switch to failover domain & retry the request
103
+ Postmen.failover!
104
+ retry
105
+ # Handle Rate limits.
106
+ # Rate limits are being reset every 60 seconds - we're retrying
107
+ # given request after that.
108
+ # @see https://docs.postmen.com/ratelimit.html Documentation
109
+ rescue RateLimitExceeded
110
+ @requests += 1
111
+ raise if @requests > MAX_REQUESTS
112
+ sleep(60)
113
+ retry
114
+ # If the resource was not found, simply re-raise the exception
115
+ rescue ResourceNotFound
116
+ raise
117
+ # Handle request errors.
118
+ # Our current error handling policy depends on the error type.
119
+ # If the API returns information, that the request is retriable,
120
+ # We're waiting 5 seconds, and trying again with exact same request.
121
+ # To prevent having infinite loops, we're trying maximum 5 times.
122
+ # In case that we were unable to make successfull request with that 5 tries,
123
+ # MaximumNumberOfRetriesReachedError is being raised.
124
+ #
125
+ # @raise RequestError if the request is not retriable
126
+ # @raise MaximumNumberOfRetriesReachedError if the API returned error after 5 retries
127
+ rescue RequestError => error
128
+ raise unless error.retriable?
129
+ raise MaximumNumberOfRetriesReachedError, self if @requests > MAX_REQUESTS
130
+ @requests += 1
131
+ sleep(5)
132
+ retry
133
+ end
134
+ end
135
+
62
136
  def raw_get(path, options)
63
137
  HTTP
64
138
  .headers(headers)
@@ -0,0 +1,52 @@
1
+ class Postmen
2
+ # Generic Exception class, all other exceptions should inherit from this class
3
+ Error = Class.new(StandardError)
4
+
5
+ # Generic exception raised if the API returns an error
6
+ class RequestError < Error
7
+ extend Forwardable
8
+
9
+ def_delegators :@request, :meta, :data
10
+
11
+ def initialize(request)
12
+ @request = request
13
+ end
14
+
15
+ # Indicates whether request is retryable.
16
+ # @see https://docs.postmen.com/errors.html API Documentation
17
+ def retryable?
18
+ meta.fetch(:retryable, false)
19
+ end
20
+
21
+ # Returns details for the request error
22
+ # @see https://docs.postmen.com/#meta API Documentation
23
+ def details
24
+ meta[:details]
25
+ end
26
+
27
+ # Internal API error code
28
+ # @see https://docs.postmen.com/errors.html List of Error codes
29
+ def code
30
+ meta[:code]
31
+ end
32
+
33
+ # Returns human-readable error message
34
+ # @see https://docs.postmen.com/#meta API Documentation
35
+ def message
36
+ meta[:message]
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :request
42
+ end
43
+
44
+ # Exception raised if rate limit was exceeded
45
+ RateLimitExceeded = Class.new(Error)
46
+ # Exception raised in case of any connection error
47
+ ConnectionError = Class.new(Error)
48
+ # Exception raised if retriable request reached maximum number of attempts.
49
+ MaximumNumberOfRetriesReachedError = Class.new(Error)
50
+ # Expcetion raised if resource was not found.
51
+ ResourceNotFound = Class.new(RequestError)
52
+ end
@@ -16,12 +16,24 @@ class Postmen
16
16
  @meta ||= parsed_response[:meta]
17
17
  end
18
18
 
19
+ # Holds the data
20
+ # @see https://docs.postmen.com/#data API Documentation
21
+ # @return [Hash]
22
+ def data
23
+ @data ||= parsed_response[:data]
24
+ end
25
+
19
26
  # Parses the json response
20
27
  # @return [Hash]
21
28
  def parsed_response
22
29
  @parsed_response ||= JSON.parse(body, symbolize_names: true)
23
30
  end
24
31
 
32
+ # Checks if response were successfull
33
+ def success?
34
+ meta[:code] == 200
35
+ end
36
+
25
37
  # Checks if rate limit was exceeded
26
38
  def rate_limit_exceeded?
27
39
  code == 429
@@ -47,11 +47,46 @@ class Postmen
47
47
  #
48
48
  # @see https://docs.postmen.com/api.html#shipper-accounts-update-a-shipper-account-credentials
49
49
  # @return [ShipperAccount] Updated ShipperAccount resource
50
- def update_credentials(params = {})
51
- Connection.new.put(
50
+ # @raise [RequestError]
51
+ def update_credentials!(params = {})
52
+ response = Connection.new.put(
52
53
  "/shipper-accounts/#{@id}/credentials",
53
- ShipperAccountUpdateCredentialsQuery.new(params).to_hash
54
+ ShipperAccountUpdateCredentialsQuery.new(params).to_query
55
+ )
56
+
57
+ raise RequestError, response unless response.success?
58
+
59
+ ShipperAccount.new(response.data)
60
+ end
61
+
62
+ # Update a ShipperAccount credentials
63
+ #
64
+ # @see https://docs.postmen.com/api.html#shipper-accounts-update-a-shipper-account-credentials
65
+ # @return [ShipperAccount] Updated ShipperAccount resource
66
+ # @return [Hash] a Hash with detailed information about what went wrong
67
+ def update_credentials(params = {})
68
+ update_credentials!(params)
69
+ rescue RequestError => error
70
+ error.meta
71
+ end
72
+
73
+ # Update a shipper account information
74
+ #
75
+ # @see https://docs.postmen.com/api.html#shipper-accounts-update-a-shipper-account-information API Documentation
76
+ # @example
77
+ # .update(description: "Your new description")
78
+ # .update(address: {})
79
+ # @return [ShipperAccount] Updated ShipperAccount resource
80
+ # @raise [RequestError]
81
+ def update!(params = {})
82
+ response = Connection.new.put(
83
+ "/shipper-accounts/#{@id}/info",
84
+ ShipperAccountUpdateQuery.new(params.merge(subject: self)).to_query
54
85
  )
86
+
87
+ raise RequestError, response unless response.success?
88
+
89
+ ShipperAccount.new(response.data)
55
90
  end
56
91
 
57
92
  # Update a shipper account information
@@ -61,8 +96,11 @@ class Postmen
61
96
  # .update(description: "Your new description")
62
97
  # .update(address: {})
63
98
  # @return [ShipperAccount] Updated ShipperAccount resource
99
+ # @return [Hash] a Hash with detailed information about what went wrong
64
100
  def update(params = {})
65
- Connection.new.put("/shipper-accounts/#{@id}/info", ShipperAccountUpdateQuery.new(params.merge(subject: self)).to_query)
101
+ update!(params)
102
+ rescue RequestError => error
103
+ error.meta
66
104
  end
67
105
  end
68
106
  end
@@ -33,7 +33,12 @@ class Postmen
33
33
  # @see https://docs.postmen.com/api.html#shipper-accounts-create-a-shipper-account API documentation
34
34
  # @return [ShipperAccount]
35
35
  def self.create(params)
36
- ShipperAccount.new(Connection.new.post('/shipper-accounts', CreateShipperAccountQuery.new(params).to_query).parsed_response[:data])
36
+ ShipperAccount.new(
37
+ Connection.new.post(
38
+ '/shipper-accounts',
39
+ CreateShipperAccountQuery.new(params).to_query
40
+ ).parsed_response[:data]
41
+ )
37
42
  end
38
43
  end
39
44
  end
@@ -1,4 +1,4 @@
1
1
  class Postmen
2
2
  # SDK Version
3
- VERSION = '1.0.0-alpha.1'.freeze
3
+ VERSION = '1.0.0-alpha.2'.freeze
4
4
  end
data/lib/postmen.rb CHANGED
@@ -6,6 +6,7 @@ require 'pathname'
6
6
  require 'forwardable'
7
7
 
8
8
  require 'postmen/version'
9
+ require 'postmen/errors'
9
10
  require 'postmen/types'
10
11
  require 'postmen/connection'
11
12
  require 'postmen/collection_proxy'
@@ -37,17 +38,6 @@ require 'postmen/manifest_collection'
37
38
  class Postmen
38
39
  extend Dry::Configurable
39
40
 
40
- # Generic Exception class, all other exceptions should inherit from this class
41
- Error = Class.new(StandardError)
42
- # Exception raised if rate limit was exceeded
43
- RateLimitExceeded = Class.new(Error)
44
- # Exception raised in case of any connection error
45
- ConnectionError = Class.new(Error)
46
- # Generic exception raised if the API returns an error
47
- RequestError = Class.new(Error)
48
- # Expcetion raised if resource was not found.
49
- ResourceNotFound = Class.new(RequestError)
50
-
51
41
  # @#!attribute [rw] api_key [String] API key taken from the application.
52
42
  setting :api_key
53
43
 
@@ -57,11 +47,14 @@ class Postmen
57
47
  # @#!attribute endpoint [String] Endoint name - specify if you'd like to use custom endpoint
58
48
  setting :endpoint
59
49
 
50
+ # @#!attribute failover [Bool] Indicates if the SDK is using failover domain.
51
+ setting :failover, false
52
+
60
53
  # Returns the endpoint used in all queries
61
54
  #
62
55
  # @return [String] endpoint url
63
56
  def self.endpoint
64
- config.endpoint || "https://#{config.region}-api.postmen.com/v3"
57
+ config.endpoint || Connection.endpoint("#{config.region}-api", config.failover)
65
58
  end
66
59
 
67
60
  # Returns path where gem is installed
@@ -70,4 +63,14 @@ class Postmen
70
63
  def self.root
71
64
  Pathname.new(File.expand_path(File.join(File.dirname(__FILE__), '../')))
72
65
  end
66
+
67
+ # Checks wheter we're in failover mode
68
+ def self.failover?
69
+ !!config.failover
70
+ end
71
+
72
+ # Switch to failover domain
73
+ def self.failover!
74
+ config.failover = true
75
+ end
73
76
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postmen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.alpha.1
4
+ version: 1.0.0.pre.alpha.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - postmen.com
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-03 00:00:00.000000000 Z
11
+ date: 2017-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http
@@ -161,6 +161,7 @@ files:
161
161
  - lib/postmen.rb
162
162
  - lib/postmen/collection_proxy.rb
163
163
  - lib/postmen/connection.rb
164
+ - lib/postmen/errors.rb
164
165
  - lib/postmen/label.rb
165
166
  - lib/postmen/label_collection.rb
166
167
  - lib/postmen/manifest.rb