postmen 0.0.0 → 0.1.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.
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