postmen 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 69b96807bb918ab129964560c96ecc008d9995b2
4
- data.tar.gz: 7fc927aab17ea0aac6752862fe133bcfbb5de830
3
+ metadata.gz: e0c166c6e9e1ba64fa64b643d122c137684d1405
4
+ data.tar.gz: e0df26f3ce8ee0d14c662cdcc776d143c59abc7e
5
5
  SHA512:
6
- metadata.gz: d4422ea111c232423449c6a91eeb32482ccf4011724688eacd92da33c9e4b15ef5dff443e41cbb0362299749057c57978449f83a5d84af9c81e0108886337c66
7
- data.tar.gz: 7cc0b50e65fd84c532957798f6aef459906f4f0d7cbb12dc6c744321bcbb4cec20e4ac9c9fcb699fc8667d4c3a971c6e8de37320c7f478f3cc966a23e23f567f
6
+ metadata.gz: bbe91de55327692811997e3402792f4a3e45520dccd0a91feb81f7d42ffc55a6e01e312570f251965b96194b6ec3297ac9feb55305af629fedfbba346de99c08
7
+ data.tar.gz: de33322a0d03cfe24e5c96405503eb165c82273912b21032de64348601b4bd0bdd1f267d0cee7e245056e856f913caac61fee4e853d8c21bec6d7c7344780cb5
data/README.md CHANGED
@@ -1,14 +1,18 @@
1
1
  ## postmen-ruby
2
2
 
3
+ [![Build Status](https://travis-ci.org/postmen/postmen-sdk-ruby.svg?branch=master)](https://travis-ci.org/postmen/postmen-sdk-ruby)
4
+ [![Code Climate](https://codeclimate.com/github/postmen/postmen-sdk-ruby/badges/gpa.svg)](https://codeclimate.com/github/postmen/postmen-sdk-ruby)
5
+ [![Gem Version](https://badge.fury.io/rb/postmen.svg)](https://badge.fury.io/rb/postmen)
6
+ [![Dependency Status](https://gemnasium.com/badges/github.com/postmen/postmen-sdk-ruby.svg)](https://gemnasium.com/github.com/postmen/postmen-sdk-ruby)
7
+
3
8
  Ruby Gem for Postmen API.
4
9
 
5
10
  This extension helps developers to integrate with Postmen easily.
6
11
 
7
- ## About Postmen
8
-
12
+ ## Early development stage
9
13
 
14
+ This gem is on early stage of development - There are a lot of features that are not covered yet, you may also expect heavy changes in the public API, before we hit version `1.0.0` (see the Milestones section)
10
15
 
11
- ### Changes
12
16
 
13
17
 
14
18
  ## Installation
@@ -27,9 +31,61 @@ This extension helps developers to integrate with Postmen easily.
27
31
 
28
32
  ## Configuration
29
33
 
34
+ ```ruby
35
+
36
+ Postmen.configure do |config|
37
+ config.region = 'sandbox' # set 'sandbox' or 'production. Required
38
+ config.api_key = 'YOUR API KEY' # Required
39
+ config.endpoint = 'http://my-custom-endpoint.example.com' # Optionally set custom endpoint url.
40
+ end
41
+
42
+ ```
30
43
 
31
44
  ## Usage
32
45
 
46
+ ```ruby
47
+ require 'postmen'
48
+
49
+ # Configure postmen first, see Configuration section
50
+
51
+ ### Fetch all labels:
52
+
53
+ Postmen::Label.all # Returns an instance of Postmen::LabelCollection
54
+
55
+ # optionally you can pass additional params to the query:
56
+
57
+ Postmen::Label.all(shipper_account_id: '1111')
58
+ Postmen::Label.all(status: 'created')
59
+ # For more options, please see the documentation: https://docs.postmen.com/api.html#labels-list-all-labels
60
+
61
+ ### Fetch single label
62
+
63
+ Postmen::Label.find('1111') # Returns an instance of Postmen::Label
64
+ ```
65
+
66
+ ### Label
67
+
68
+ An instance of `Postmen::Label` responds to following methods:
69
+
70
+ `id`,
71
+ `status`,
72
+ `tracking_numbers`
73
+ `files`,
74
+ `rate`
75
+
76
+ ### LabelCollection
77
+
78
+ `Postmen::LabelCollection` acts as an Array of `Label` instances. You can do things like:
79
+
80
+ ```ruby
81
+ collection = Postmen::Label.all
82
+
83
+ collection.first # Returns an instance of Label
84
+ collection.size # Returns number of elements returned
85
+ collection.each{|label| puts label.inspect } # Iterates over labels
86
+ collection.select{|label| } # Selects labels by given criteria
87
+ ```
88
+ and so on.
33
89
 
34
90
  ## The License (MIT)
35
91
 
@@ -0,0 +1,35 @@
1
+ class Postmen
2
+ module CollectionProxy
3
+ include Enumerable
4
+ extend Forwardable
5
+
6
+ module ClassMethods
7
+ attr_reader :settings
8
+
9
+ def model(model = nil)
10
+ return settings[:model] unless model
11
+ settings[:model] = model
12
+ end
13
+
14
+ def key(key = nil)
15
+ return settings[:key] unless key
16
+ settings[:key] = key
17
+ end
18
+
19
+ def get(response)
20
+ model.new(response[:data])
21
+ end
22
+ end
23
+
24
+ def self.included(base)
25
+ base.extend ClassMethods
26
+ base.instance_variable_set('@settings', {})
27
+ end
28
+
29
+ def_delegators :@data, :[], :each
30
+
31
+ def initialize(response)
32
+ @data = response[:data][self.class.key].map { |element| self.class.model.new(element) }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ class Postmen
2
+ class Connection
3
+ MAX_REQUESTS = 5
4
+
5
+ def initialize
6
+ @requests = 0
7
+ end
8
+
9
+ def get(path, options = {})
10
+ Response.new(raw_get(path, options)).tap(&:parse_response!)
11
+ rescue RateLimitExceeded
12
+ @requests += 1
13
+ raise if @requests > MAX_REQUESTS
14
+ sleep(60)
15
+ retry
16
+ end
17
+
18
+ def post(path, options = {})
19
+ Response.new(raw_post(path, options))
20
+ end
21
+
22
+ private
23
+
24
+ def raw_get(path, options)
25
+ HTTP
26
+ .headers(headers)
27
+ .get(get_full_url(path), options)
28
+ end
29
+
30
+ def raw_post(path, options)
31
+ HTTP
32
+ .headers(headers)
33
+ .post(get_full_url(path), options)
34
+ end
35
+
36
+ def get_full_url(path)
37
+ [Postmen.endpoint, path].join
38
+ end
39
+
40
+ def headers
41
+ {
42
+ "content-type": 'application/json',
43
+ "postmen-api-key": Postmen.config.api_key
44
+ }
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,21 @@
1
+ class Postmen
2
+ class Label < Dry::Struct
3
+ attribute :id, Types::UUID
4
+ attribute :status, Types::String
5
+ attribute :tracking_numbers, Types::Array.member(Types::String)
6
+ attribute :files, Types::Strict::Nil | Types::Array.member(Types::Hash)
7
+ attribute :rate, Types::Strict::Nil | Types::Hash
8
+
9
+ def self.all(options = {})
10
+ LabelCollection.all(options)
11
+ end
12
+
13
+ def self.find(id)
14
+ LabelCollection.find(id)
15
+ end
16
+
17
+ def self.create(params)
18
+ LabelCollection.create(params)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ class Postmen
2
+ class LabelCollection
3
+ include CollectionProxy
4
+
5
+ model Label
6
+ key :labels
7
+
8
+ def self.all(options = {})
9
+ new(Connection.new.get('/labels', LabelQuery.new(options).to_query).parsed_response)
10
+ end
11
+
12
+ def self.find(id)
13
+ get(Connection.new.get("/labels/#{id}").parsed_response)
14
+ end
15
+
16
+ def self.create(params)
17
+ Label.new(Connection.new.post('/labels', CreateLabelQuery.new(params).to_query).parsed_response[:data])
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ class Postmen
2
+ class CreateLabelQuery < Dry::Struct
3
+ constructor_type :strict_with_defaults
4
+
5
+ attribute :service_type, Types::String
6
+ attribute :shipper_account, Types::Reference
7
+ attribute :shipment, Types::Shipment
8
+ attribute :async, Types::Bool.optional.default(nil)
9
+ attribute :return_shipment, Types::Bool.optional.default(nil)
10
+ attribute :paper_size, Types::PaperSize.optional.default(nil)
11
+ attribute :ship_date, Types::String.optional.default(nil)
12
+ # attribute :service_options
13
+ attribute :is_document, Types::Bool.optional.default(nil)
14
+ attribute :invoice, Types::Invoice.optional.default(nil)
15
+ attribute :billing, Types::Billing.optional.default(nil)
16
+ attribute :customs, Types::Customs.optional.default(nil)
17
+
18
+ def to_query
19
+ {
20
+ json: query
21
+ }
22
+ end
23
+
24
+ private
25
+
26
+ def query
27
+ to_hash.reject { |_k, v| v.nil? }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ class Postmen
2
+ class LabelQuery < Dry::Struct
3
+ DATE_FORMAT = '%FT%T%:z'.freeze
4
+
5
+ constructor_type :schema
6
+
7
+ attribute :shipper_account_id, Types::String.optional
8
+ attribute :status, Types::Strict::Nil | Types::Statuses
9
+ attribute :limit, Types::String.optional
10
+ attribute :created_at_min, Types::DateTime.maybe
11
+ attribute :created_at_max, Types::DateTime.maybe
12
+ attribute :tracking_numbers, Types::String.maybe
13
+ attribute :next_token, Types::String.optional
14
+
15
+ def to_query
16
+ {
17
+ params: query
18
+ }
19
+ end
20
+
21
+ private
22
+
23
+ def format_date(date)
24
+ date.fmap { |d| d.strftime(DATE_FORMAT) }.value
25
+ end
26
+
27
+ def formatted_tracking_numbers
28
+ tracking_numbers.fmap { |numbers| Array(numbers).join(',') }.value
29
+ end
30
+
31
+ def query
32
+ {
33
+ shipper_account_id: shipper_account_id,
34
+ status: status,
35
+ limit: limit,
36
+ created_at_min: format_date(created_at_min),
37
+ created_at_max: format_date(created_at_max),
38
+ tracking_numbers: formatted_tracking_numbers,
39
+ next_token: next_token
40
+ }.reject { |_k, v| v.nil? }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ class Postmen
2
+ class Response < SimpleDelegator
3
+ def parse_response!
4
+ ensure_rate_limit!
5
+ ensure_resource_found!
6
+ end
7
+
8
+ def meta
9
+ @meta ||= parsed_response[:meta]
10
+ end
11
+
12
+ def parsed_response
13
+ @parsed_response ||= JSON.parse(body, symbolize_names: true)
14
+ end
15
+
16
+ def rate_limit_exceeded?
17
+ code == 429
18
+ end
19
+
20
+ def remaining_api_calls
21
+ Integer(headers['X-RateLimit-Remaining'])
22
+ end
23
+
24
+ def api_rate_limit
25
+ Integer(headers['X-RateLimit-Limit'])
26
+ end
27
+
28
+ def api_rate_limit_reset
29
+ Integer(headers['X-RateLimit-Reset'])
30
+ end
31
+
32
+ def api_rate_limit_reset_at
33
+ Time.at(api_rate_limit_reset)
34
+ end
35
+
36
+ private
37
+
38
+ def ensure_rate_limit!
39
+ raise RateLimitExceeded, self if rate_limit_exceeded?
40
+ end
41
+
42
+ def ensure_resource_found!
43
+ raise ResourceNotFound, self if meta[:code] == 4153
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+ Dry::Types.load_extensions(:maybe)
2
+
3
+ class Postmen
4
+ module Types
5
+ include Dry::Types.module
6
+
7
+ UUID = Types::Strict::String.constrained(
8
+ format: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
9
+ )
10
+
11
+ Statuses = Types::String.enum(
12
+ 'creating',
13
+ 'created',
14
+ 'cancelling',
15
+ 'cancelled',
16
+ 'manifesting',
17
+ 'manifested',
18
+ 'failed'
19
+ )
20
+
21
+ Reference = Types::Hash.schema(id: Types::UUID)
22
+ Shipment = Types::Hash
23
+ PaperSize = Types::String.enum(
24
+ '4x4',
25
+ '4x6',
26
+ '4x6.75',
27
+ '4x8',
28
+ '6x4',
29
+ 'a4',
30
+ 'default'
31
+ )
32
+
33
+ Invoice = Types::Hash
34
+ Billing = Types::Hash
35
+ Customs = Types::Hash
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ class Postmen
2
+ VERSION = '0.1.0'.freeze
3
+ end
data/lib/postmen.rb CHANGED
@@ -1,9 +1,38 @@
1
- $:.unshift File.dirname(__FILE__)
1
+ require 'dry-configurable'
2
+ require 'dry-struct'
3
+ require 'http'
4
+ require 'json'
5
+ require 'pathname'
6
+ require 'forwardable'
2
7
 
3
- module Postmen
4
- class << self;
5
- attr_accessor :api_key
8
+ require 'postmen/version'
9
+ require 'postmen/types'
10
+ require 'postmen/connection'
11
+ require 'postmen/collection_proxy'
12
+ require 'postmen/label'
13
+ require 'postmen/label_collection'
14
+ require 'postmen/response'
15
+ require 'postmen/query/label_query'
16
+ require 'postmen/query/create_label_query'
17
+
18
+ class Postmen
19
+ extend Dry::Configurable
20
+
21
+ Error = Class.new(StandardError)
22
+ RateLimitExceeded = Class.new(Error)
23
+ ConnectionError = Class.new(Error)
24
+ RequestError = Class.new(Error)
25
+ ResourceNotFound = Class.new(RequestError)
26
+
27
+ setting :api_key
28
+ setting :region
29
+ setting :endpoint
30
+
31
+ def self.endpoint
32
+ config.endpoint || "https://#{config.region}-api.postmen.com/v3"
6
33
  end
7
34
 
8
- URL = ENV['POSTMEN_API_ENDPOINT'] || 'https://api.postmen.com'
35
+ def self.root
36
+ Pathname.new(File.expand_path(File.join(File.dirname(__FILE__), '../')))
37
+ end
9
38
  end
metadata CHANGED
@@ -1,57 +1,127 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: postmen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - postmen.com
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-15 00:00:00.000000000 Z
11
+ date: 2017-02-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: httpclient
14
+ name: http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-configurable
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - "~>"
18
32
  - !ruby/object:Gem::Version
19
- version: 2.5.2
33
+ version: '0.5'
20
34
  type: :runtime
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
- version: 2.5.2
40
+ version: '0.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-struct
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dry-monads
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.2'
27
69
  - !ruby/object:Gem::Dependency
28
70
  name: rspec
29
71
  requirement: !ruby/object:Gem::Requirement
30
72
  requirements:
31
73
  - - "~>"
32
74
  - !ruby/object:Gem::Version
33
- version: 2.14.1
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.3'
34
104
  type: :development
35
105
  prerelease: false
36
106
  version_requirements: !ruby/object:Gem::Requirement
37
107
  requirements:
38
108
  - - "~>"
39
109
  - !ruby/object:Gem::Version
40
- version: 2.14.1
110
+ version: '2.3'
41
111
  - !ruby/object:Gem::Dependency
42
- name: pry
112
+ name: rake
43
113
  requirement: !ruby/object:Gem::Requirement
44
114
  requirements:
45
- - - ">="
115
+ - - ">"
46
116
  - !ruby/object:Gem::Version
47
- version: '0'
117
+ version: '10.0'
48
118
  type: :development
49
119
  prerelease: false
50
120
  version_requirements: !ruby/object:Gem::Requirement
51
121
  requirements:
52
- - - ">="
122
+ - - ">"
53
123
  - !ruby/object:Gem::Version
54
- version: '0'
124
+ version: '10.0'
55
125
  description: Developed for easy integration with Postmen
56
126
  email:
57
127
  - support@postmen.com
@@ -61,6 +131,15 @@ extra_rdoc_files: []
61
131
  files:
62
132
  - README.md
63
133
  - lib/postmen.rb
134
+ - lib/postmen/collection_proxy.rb
135
+ - lib/postmen/connection.rb
136
+ - lib/postmen/label.rb
137
+ - lib/postmen/label_collection.rb
138
+ - lib/postmen/query/create_label_query.rb
139
+ - lib/postmen/query/label_query.rb
140
+ - lib/postmen/response.rb
141
+ - lib/postmen/types.rb
142
+ - lib/postmen/version.rb
64
143
  homepage: https://www.postmen.com
65
144
  licenses:
66
145
  - MIT
@@ -73,16 +152,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
152
  requirements:
74
153
  - - ">="
75
154
  - !ruby/object:Gem::Version
76
- version: 1.8.7
155
+ version: 2.2.0
77
156
  required_rubygems_version: !ruby/object:Gem::Requirement
78
157
  requirements:
79
158
  - - ">="
80
159
  - !ruby/object:Gem::Version
81
160
  version: '0'
82
- requirements:
83
- - none
161
+ requirements: []
84
162
  rubyforge_project:
85
- rubygems_version: 2.2.2
163
+ rubygems_version: 2.5.1
86
164
  signing_key:
87
165
  specification_version: 4
88
166
  summary: Postmen