easyship 0.5.1 → 0.6.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
  SHA256:
3
- metadata.gz: 48a6c8c265dc7c60e8b1b749032f29632ba74b79df21192bc8e7c5a2a80aea26
4
- data.tar.gz: e6e52aba415127500177c16db9e1e99c931fda0bcc5642e60e69dc973efae886
3
+ metadata.gz: 90edef7622104c80daa116acd3a8511e63939faa885ad87913a2749d554f75c6
4
+ data.tar.gz: a771f5e0d156e2aaf7a4dd8603116572d870903b3fb1d6ab5de1e638a55881eb
5
5
  SHA512:
6
- metadata.gz: 821d88416be393652174144a6ae29161ee7c4b43cde9c478515653365f1d84b7c9e11fc7b7ea4e5bd75b932cac3b672d572eb173ba2fab2e01f9e30286edaf42
7
- data.tar.gz: 16eba98f3e5de4334a8d182e96262da79eb3dcfc1d61709652fb315603971466a1fb42ed65dfe9ebae9f72c747892e7ca20a3c3a83075640af88a1989321d6a4
6
+ metadata.gz: fffafc0e173ec627975fc5acb4ead24044a3633ca66d19255e3fc6f1e1180d4ec733a1464327aa491f5a4710d7d5b802334d225912d203a8ad15c278e22eef4b
7
+ data.tar.gz: 2af5b4ef1969f9dc05595fda224688f0ec1787f2fe3ca516c6119dc4c6f052afc4cbafcf074d3d8a0451e5df16b1a0c957fa385990fe6243237d59f0935a8ad0
data/.rubocop.yml CHANGED
@@ -14,4 +14,8 @@ RSpec/ExampleLength:
14
14
  Max: 10
15
15
 
16
16
  RSpec/NestedGroups:
17
- Max: 5
17
+ Max: 5
18
+
19
+ RSpec/SpecFilePathFormat:
20
+ Exclude:
21
+ - 'spec/integration/**/*_spec.rb'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [v0.6.0](https://github.com/mmarusyk/easyship/tree/v0.6.0) - 2026-02-28
4
+
5
+ ### Added
6
+ - Secure logging module with configurable logger support
7
+ - `NullLogger` as default (quiet by default) to avoid log pollution
8
+ - Abstract logging methods: `log_info`, `log_debug`, `log_warn`, `log_error`, `log_request`, `log_response`
9
+ - Logger configuration option to allow custom loggers (Rails.logger, custom Logger, etc.)
10
+ - Integration tests for logging functionality
11
+ - Security-first logging: no API keys, headers, or request/response bodies logged
12
+
3
13
  ## [v0.5.1](https://github.com/mmarusyk/easyship/tree/v0.5.1) - 2026-01-24
4
14
 
5
15
  ### Fixed
data/CLAUDE.md ADDED
@@ -0,0 +1,116 @@
1
+ # CLAUDE Instructions
2
+
3
+ ## Release Instruction
4
+
5
+ ### 1. Ensure you are on `main` and it is up-to-date
6
+
7
+ ```bash
8
+ git checkout main
9
+ git pull origin main
10
+ ```
11
+
12
+ ---
13
+
14
+ ### 2. Determine the next version
15
+
16
+ Follow [Semantic Versioning](https://semver.org/):
17
+
18
+ | Change type | Example bump |
19
+ |---|---|
20
+ | Bug fixes only | `0.5.1` → `0.5.2` |
21
+ | New backward-compatible features | `0.5.1` → `0.6.0` |
22
+ | Breaking changes | `0.5.1` → `1.0.0` |
23
+
24
+ Current version is in `lib/easyship/version.rb`. Review the `[Unreleased]` section of `CHANGELOG.md` to decide the correct bump type.
25
+
26
+ ---
27
+
28
+ ### 3. Update the version constant
29
+
30
+ Edit `lib/easyship/version.rb` and set the new version:
31
+
32
+ ```ruby
33
+ module Easyship
34
+ VERSION = 'X.Y.Z' # replace with the new version
35
+ end
36
+ ```
37
+
38
+ ---
39
+
40
+ ### 4. Update `CHANGELOG.md`
41
+
42
+ The changelog follows [Keep a Changelog](https://keepachangelog.com/) format.
43
+
44
+ - Rename the `## [Unreleased]` section header to `## [vX.Y.Z](https://github.com/mmarusyk/easyship/tree/vX.Y.Z) - YYYY-MM-DD`
45
+ - Use today's date in `YYYY-MM-DD` format.
46
+ - Add a new empty `## [Unreleased]` section at the top (above the new version section).
47
+
48
+ **Before:**
49
+ ```markdown
50
+ ## [Unreleased]
51
+
52
+ ### Added
53
+ - Some new feature
54
+ ```
55
+
56
+ **After:**
57
+ ```markdown
58
+ ## [Unreleased]
59
+
60
+ ## [v0.6.0](https://github.com/mmarusyk/easyship/tree/v0.6.0) - 2026-02-28
61
+
62
+ ### Added
63
+ - Some new feature
64
+ ```
65
+
66
+ ---
67
+
68
+ ### 5. Run bundle install and tests with linting
69
+
70
+ ```bash
71
+ bundle install
72
+ bundle exec rake
73
+ ```
74
+
75
+ This runs both the test suite (`rspec`) and linter (`rubocop`). **Do not proceed if either fails.** Fix all issues before continuing.
76
+
77
+ ---
78
+
79
+ ### 6. Commit the release changes
80
+
81
+ Stage only the version and changelog files:
82
+
83
+ ```bash
84
+ git add lib/easyship/version.rb CHANGELOG.md Gemfile.lock
85
+ git commit -m "Release vX.Y.Z"
86
+ ```
87
+
88
+ The commit message must follow the pattern `Release vX.Y.Z` exactly (e.g. `Release v0.6.0`).
89
+
90
+ ---
91
+
92
+ ### 7. Push the commit to `main`
93
+
94
+ ```bash
95
+ git push origin main
96
+ ```
97
+
98
+ ---
99
+
100
+ ### 8. Create and push the version tag
101
+
102
+ ```bash
103
+ git tag vX.Y.Z
104
+ git push origin vX.Y.Z
105
+ ```
106
+
107
+ Pushing a tag matching `v*` automatically triggers the `.github/workflows/release.yml` GitHub Actions workflow, which builds and publishes the gem to RubyGems.org using trusted publishing (no manual API key needed).
108
+
109
+ ---
110
+
111
+ ### 9. Verify the release
112
+
113
+ - Check the [Actions tab](https://github.com/mmarusyk/easyship/actions) to confirm the `Release Gem` workflow completed successfully.
114
+ - Confirm the new version appears on [RubyGems.org](https://rubygems.org/gems/easyship).
115
+
116
+ ---
data/README.md CHANGED
@@ -58,7 +58,72 @@ Easyship.configure do |config|
58
58
  end
59
59
  ```
60
60
 
61
- Configuration supports the next keys: `url`, `api_key`, `per_page`, `requests_per_second`, `requests_per_minute`, `headers`.
61
+ Configuration supports the next keys: `url`, `api_key`, `per_page`, `requests_per_second`, `requests_per_minute`, `headers`, `logger`.
62
+
63
+ ### Logging
64
+
65
+ The gem includes secure, professional logging capabilities that are **quiet by default**. Logging is designed to be abstract and never exposes sensitive data like API keys, headers, or request/response bodies.
66
+
67
+ **Default Behavior (Quiet & Secure):**
68
+ ```ruby
69
+ # By default, logging is disabled (NullLogger)
70
+ Easyship.configure do |config|
71
+ config.url = 'api_url'
72
+ config.api_key = 'your_easyship_api_key'
73
+ # No logging output - completely silent
74
+ end
75
+ ```
76
+
77
+ **Using Rails Logger:**
78
+ ```ruby
79
+ # In config/initializers/easyship.rb
80
+ Easyship.configure do |config|
81
+ config.url = 'api_url'
82
+ config.api_key = 'your_easyship_api_key'
83
+ config.logger = Rails.logger
84
+ end
85
+ ```
86
+
87
+ **Using Custom Logger:**
88
+ ```ruby
89
+ require 'logger'
90
+
91
+ custom_logger = Logger.new(STDOUT)
92
+ custom_logger.level = Logger::INFO
93
+
94
+ Easyship.configure do |config|
95
+ config.url = 'api_url'
96
+ config.api_key = 'your_easyship_api_key'
97
+ config.logger = custom_logger
98
+ end
99
+ ```
100
+
101
+ **Log File Output:**
102
+ ```ruby
103
+ Easyship.configure do |config|
104
+ config.url = 'api_url'
105
+ config.api_key = 'your_easyship_api_key'
106
+ config.logger = Logger.new('log/easyship.log')
107
+ end
108
+ ```
109
+
110
+ **Example Log Output:**
111
+ ```
112
+ [Easyship] GET /2023-01/account
113
+ [Easyship] Response 200 (0.245s)
114
+ [Easyship] POST /2023-01/shipments
115
+ [Easyship] Response 201 (0.432s)
116
+ [Easyship] Error: InvalidTokenError - Invalid API token | Context: {:method=>:get, :path=>"/2023-01/account"}
117
+ ```
118
+
119
+ **Extensibility:**
120
+ You can extend logger module for custom logging needs:
121
+ - `log_info(logger, message)` - General info logging
122
+ - `log_debug(logger, message)` - Debug logging
123
+ - `log_warn(logger, message)` - Warning logging
124
+ - `log_error(logger, error, context)` - Error with context
125
+ - `log_request(logger, method, path)` - Request logging
126
+ - `log_response(logger, status, duration)` - Response logging
62
127
 
63
128
  ### Making Requests
64
129
  `Easyship::Client` supports the next methods: `get`, `post`, `put`, `patch`, `delete`.
@@ -5,53 +5,71 @@ require 'faraday'
5
5
  require_relative 'middleware/error_handler_middleware'
6
6
  require_relative 'handlers/response_body_handler'
7
7
  require_relative 'pagination/cursor'
8
+ require_relative 'logging/logger'
8
9
 
9
10
  module Easyship
10
11
  # Represents a client to interact with the Easyship API
11
12
  class Client
12
13
  include Singleton
14
+ include Easyship::Logging::Logger
13
15
 
14
16
  def initialize
15
17
  @url = Easyship.configuration.url
16
18
  @api_key = Easyship.configuration.api_key
19
+ @logger = Easyship.configuration.logger
17
20
  end
18
21
 
19
22
  def get(path, params = {}, headers: {}, &block)
20
23
  if block
21
24
  Easyship::Pagination::Cursor.new(self, path, params).all(&block)
22
25
  else
23
- response = connection(headers).get(path, params)
24
-
25
- handle_response(response)
26
+ execute_request(:get, path, params, headers) do |conn|
27
+ conn.get(path, params)
28
+ end
26
29
  end
27
30
  end
28
31
 
29
32
  def post(path, params = {}, headers: {})
30
- response = connection(headers).post(path, params.to_json)
31
-
32
- handle_response(response)
33
+ execute_request(:post, path, params, headers) do |conn|
34
+ conn.post(path, params.to_json)
35
+ end
33
36
  end
34
37
 
35
38
  def put(path, params = {}, headers: {})
36
- response = connection(headers).put(path, params.to_json)
37
-
38
- handle_response(response)
39
+ execute_request(:put, path, params, headers) do |conn|
40
+ conn.put(path, params.to_json)
41
+ end
39
42
  end
40
43
 
41
44
  def delete(path, params = {}, headers: {})
42
- response = connection(headers).delete(path, params)
43
-
44
- handle_response(response)
45
+ execute_request(:delete, path, params, headers) do |conn|
46
+ conn.delete(path, params)
47
+ end
45
48
  end
46
49
 
47
50
  def patch(path, params = {}, headers: {})
48
- response = connection(headers).patch(path, params.to_json)
49
-
50
- handle_response(response)
51
+ execute_request(:patch, path, params, headers) do |conn|
52
+ conn.patch(path, params.to_json)
53
+ end
51
54
  end
52
55
 
53
56
  private
54
57
 
58
+ def execute_request(method, path, _params, headers)
59
+ log_request(@logger, method, path)
60
+
61
+ start_time = Time.now
62
+ response = yield(connection(headers))
63
+ duration = Time.now - start_time
64
+
65
+ log_response(@logger, response.status, duration)
66
+
67
+ handle_response(response)
68
+ rescue StandardError => e
69
+ log_error(@logger, e, { method: method, path: path })
70
+ raise
71
+ end
72
+
55
73
  def connection(custom_headers = {})
56
74
  Faraday.new(url: @url) do |faraday|
57
75
  faraday.request :url_encoded
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'logging/null_logger'
4
+
3
5
  module Easyship
4
6
  # Represents the configuration settings for the Easyship client.
5
7
  class Configuration
6
- attr_accessor :url, :api_key, :per_page, :requests_per_second, :requests_per_minute, :headers
8
+ attr_accessor :url, :api_key, :per_page, :requests_per_second, :requests_per_minute, :headers, :logger
7
9
 
8
10
  def initialize
9
11
  @url = nil
@@ -12,6 +14,7 @@ module Easyship
12
14
  @requests_per_second = nil
13
15
  @requests_per_minute = nil
14
16
  @headers = {}
17
+ @logger = Easyship::Logging::NullLogger.new
15
18
  end
16
19
  end
17
20
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Easyship
6
+ module Logging
7
+ # This module provides logging methods that can be included in any class
8
+ # to add secure, standardized logging capabilities.
9
+ module Logger
10
+ def log_info(logger, message)
11
+ return unless logger
12
+
13
+ logger.info("[Easyship] #{message}")
14
+ end
15
+
16
+ def log_debug(logger, message)
17
+ return unless logger
18
+
19
+ logger.debug("[Easyship] #{message}")
20
+ end
21
+
22
+ def log_warn(logger, message)
23
+ return unless logger
24
+
25
+ logger.warn("[Easyship] #{message}")
26
+ end
27
+
28
+ def log_error(logger, error, context = {})
29
+ return unless logger
30
+
31
+ logger.error do
32
+ message = "[Easyship] Error: #{error.class} - #{error.message}"
33
+ message += " | Context: #{context}" if context.any?
34
+ message += "\n#{error.backtrace.join("\n")}" if error.backtrace
35
+ message
36
+ end
37
+ end
38
+
39
+ def log_request(logger, method, path)
40
+ return unless logger
41
+
42
+ logger.info("[Easyship] #{method.to_s.upcase} #{path}")
43
+ end
44
+
45
+ def log_response(logger, status, duration = nil)
46
+ return unless logger
47
+
48
+ level = response_log_level(status)
49
+ message = "[Easyship] Response #{status}"
50
+ message += " (#{format('%.3f', duration)}s)" if duration
51
+
52
+ logger.public_send(level, message)
53
+ end
54
+
55
+ private
56
+
57
+ def response_log_level(status)
58
+ return :warn if status.between?(400, 499)
59
+ return :error if status.between?(500, 599)
60
+ return :debug if status.between?(200, 299)
61
+
62
+ :info
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Easyship
4
+ module Logging
5
+ # Implements the standard Logger interface but performs no actual logging,
6
+ # making it safe to use as a default without any output or performance impact.
7
+ class NullLogger
8
+ def debug(_message = nil, &); end
9
+
10
+ def info(_message = nil, &); end
11
+
12
+ def warn(_message = nil, &); end
13
+
14
+ def error(_message = nil, &); end
15
+
16
+ def fatal(_message = nil, &); end
17
+
18
+ def unknown(_message = nil, &); end
19
+
20
+ def level=(_level); end
21
+
22
+ def level
23
+ ::Logger::UNKNOWN
24
+ end
25
+
26
+ def tagged(*_tags)
27
+ yield self if block_given?
28
+ end
29
+
30
+ def formatter=(_formatter); end
31
+
32
+ def formatter
33
+ nil
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Easyship
4
- VERSION = '0.5.1'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/easyship.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative 'easyship/version'
4
4
  require_relative 'easyship/configuration'
5
+ require_relative 'easyship/logging/null_logger'
6
+ require_relative 'easyship/logging/logger'
5
7
  require_relative 'easyship/client'
6
8
  require_relative 'easyship/rate_limiting/rate_limiter'
7
9
  require_relative 'easyship/rate_limiting/window_rate_limiter'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easyship
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Marusyk
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-01-24 00:00:00.000000000 Z
10
+ date: 2026-02-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: faraday
@@ -35,6 +35,7 @@ files:
35
35
  - ".rspec"
36
36
  - ".rubocop.yml"
37
37
  - CHANGELOG.md
38
+ - CLAUDE.md
38
39
  - CODE_OF_CONDUCT.md
39
40
  - CONTRIBUTORS.md
40
41
  - LICENSE.txt
@@ -54,6 +55,8 @@ files:
54
55
  - lib/easyship/errors/server_error.rb
55
56
  - lib/easyship/errors/unprocessable_content_error.rb
56
57
  - lib/easyship/handlers/response_body_handler.rb
58
+ - lib/easyship/logging/logger.rb
59
+ - lib/easyship/logging/null_logger.rb
57
60
  - lib/easyship/middleware/error_handler_middleware.rb
58
61
  - lib/easyship/pagination/cursor.rb
59
62
  - lib/easyship/rate_limiting/rate_limiter.rb