amqp-client 1.2.0 → 1.2.1

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: f0abeedd581cdc09eac6afaf690fb8a50fbaef0146e4c22be43d7cad90d8d9b6
4
- data.tar.gz: 16161d756ce7d68ba24debc60eda941243bb951cd17262f1c148ef1039595e88
3
+ metadata.gz: 8cfb2dd354eeb33334cf6848b168f2a0caa486b40cefcce924910a16a77ffa4f
4
+ data.tar.gz: 7a7896809e27539ab3395b833038c5b328298c9cf6a25aeff7f706e05657e995
5
5
  SHA512:
6
- metadata.gz: 272c099f3158572304b30bbf67227343ce5456b90e5b4a08ccaf9c2da0f9b3a42ffb02913431b7d98cc0b7f1d5d36ef2c61bd492cdabf4411b0d2f941c616f04
7
- data.tar.gz: a2000478882d0e630d88fb67e09675b087981498ac9a5b7f7a80f69a092c082415530156136cca1fa2e1bf88874468437fd3dbc600ee354110fb0c6af59e1373
6
+ metadata.gz: c8d0c1797b3b8a81fbe33a154bb255c452f09d925fdeafdfc03fdbb3342b116d7875d6166f7e9b948232f09d69c16179e0270c752b3afdd955d4ad6fca3b6cd1
7
+ data.tar.gz: 47c518dee777f61435e748045fb7cfebf808ac5ed9cd53813081b222a9abc81b82f85f436482b25dba1dcd10a96c1d1f9bbf4b5266fc90ece48307b61f03ccfb
@@ -39,7 +39,14 @@ jobs:
39
39
  with:
40
40
  bundler-cache: true
41
41
  ruby-version: ${{ matrix.ruby }}
42
+ - name: Run tests (excluding TLS tests) (JRuby)
43
+ if: ${{ matrix.ruby == 'jruby' }}
44
+ continue-on-error: ${{ matrix.allow-failure || false }}
45
+ run: bundle exec rake
46
+ env:
47
+ JAVA_OPTS: "-Djava.net.preferIPv4Stack=true"
42
48
  - name: Run tests (excluding TLS tests)
49
+ if: ${{ matrix.ruby != 'jruby' }}
43
50
  continue-on-error: ${{ matrix.allow-failure || false }}
44
51
  run: bundle exec rake
45
52
  env:
@@ -12,6 +12,7 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
  permissions:
14
14
  id-token: write # for trusted publishing
15
+ contents: write # for creating releases
15
16
  steps:
16
17
  - uses: actions/checkout@v5
17
18
  - uses: ruby/setup-ruby@v1
@@ -24,3 +25,30 @@ jobs:
24
25
  - run: gem build *.gemspec
25
26
  - run: gem install *.gem
26
27
  - run: gem push *.gem
28
+
29
+ # create GitHub release
30
+ - name: Extract release notes
31
+ id: extract_release_notes
32
+ run: |
33
+ # Extract version from tag (remove 'v' prefix)
34
+ VERSION=${GITHUB_REF#refs/tags/v}
35
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
36
+
37
+ # Extract changelog section for this version with better handling
38
+ if grep -q "## \[$VERSION\]" CHANGELOG.md; then
39
+ awk "/^## \[$VERSION\]/ {flag=1; next} /^## \[/ && flag {exit} flag && /\S/ {print}" CHANGELOG.md > release_notes.md
40
+ echo "Release notes extracted for version $VERSION:"
41
+ cat release_notes.md
42
+ else
43
+ echo "No changelog entry found for version $VERSION" > release_notes.md
44
+ echo "Warning: No changelog entry found for version $VERSION"
45
+ fi
46
+
47
+ - name: Create GitHub Release
48
+ uses: softprops/action-gh-release@v2
49
+ with:
50
+ name: Release ${{ steps.extract_release_notes.outputs.version }}
51
+ body_path: release_notes.md
52
+ draft: false
53
+ prerelease: false
54
+ files: '*.gem'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.2.1] - 2025-09-15
4
+
5
+ - Added: Convenience methods for creating exchange types: `fanout()`, `direct()`, `topic()`, and `headers()`
6
+ - Added: Support for binding with high level objects (Exchange and Queue objects can now be passed as binding sources)
7
+ - Fixed: Bug where a client without any connection could not be closed properly
8
+
3
9
  ## [1.2.0] - 2025-09-10
4
10
 
5
11
  - Fixed: `Connection#channel` wasn't thread-safe
data/README.md CHANGED
@@ -127,12 +127,12 @@ To install this gem onto your local machine, run `bundle exec rake install`.
127
127
 
128
128
  ### Release Process
129
129
 
130
- The gem uses rake tasks to automate the release process. Make sure your working directory is clean before starting a release.
130
+ The gem uses rake tasks to automate the release preparation process. The actual gem building and publishing is handled automatically by GitHub Actions when a tag is pushed.
131
131
 
132
132
  #### Quick Release (Patch Version)
133
133
 
134
134
  ```bash
135
- rake release:full
135
+ rake release:prepare
136
136
  ```
137
137
 
138
138
  This will:
@@ -141,8 +141,8 @@ This will:
141
141
  2. Bump the patch version (e.g., 1.2.0 → 1.2.1)
142
142
  3. Update the CHANGELOG.md with the new version and current date
143
143
  4. Create a git commit and tag for the release
144
- 5. Build and push the gem to RubyGems
145
- 6. Push commits and tags to the remote repository
144
+ 5. Push commits and tags to the remote repository
145
+ 6. GitHub Actions will automatically build and publish the gem to RubyGems
146
146
 
147
147
  #### Custom Version Bump
148
148
 
@@ -150,10 +150,10 @@ For minor or major version bumps:
150
150
 
151
151
  ```bash
152
152
  # Minor version bump (e.g., 1.2.0 → 1.3.0)
153
- rake release:full[minor]
153
+ rake release:prepare[minor]
154
154
 
155
155
  # Major version bump (e.g., 1.2.0 → 2.0.0)
156
- rake release:full[major]
156
+ rake release:prepare[major]
157
157
  ```
158
158
 
159
159
  #### Individual Release Steps
@@ -167,11 +167,11 @@ rake release:bump[patch] # or [minor] or [major]
167
167
  # Update changelog with current version
168
168
  rake release:changelog
169
169
 
170
- # Create git tag
170
+ # Create git tag with changelog entries
171
171
  rake release:tag
172
172
 
173
- # Build and push to RubyGems
174
- rake release:push
173
+ # Push tag to remote (handles conflicts)
174
+ rake release:push_tag
175
175
  ```
176
176
 
177
177
  #### Manual Release Steps
@@ -182,7 +182,7 @@ If you prefer manual control:
182
182
  2. Update the CHANGELOG.md with the new version and release notes
183
183
  3. Commit your changes: `git add . && git commit -m "Release X.Y.Z"`
184
184
  4. Create and push a tag: `git tag vX.Y.Z && git push origin vX.Y.Z`
185
- 5. Build and push: `rake build && gem push amqp-client-X.Y.Z.gem`
185
+ 5. GitHub Actions will automatically build and publish the gem when the tag is pushed
186
186
 
187
187
  ## Contributing
188
188
 
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
3
  require "rake/testtask"
5
4
 
6
5
  Rake::TestTask.new(:test) do |t|
@@ -31,6 +30,36 @@ def current_version
31
30
  content.match(/VERSION = "(.+)"/)[1]
32
31
  end
33
32
 
33
+ def extract_changelog_for_version(version)
34
+ changelog = File.read("CHANGELOG.md")
35
+
36
+ # Find the section for this version
37
+ version_pattern = /^## \[#{Regexp.escape(version)}\][^\n]*\n(.*?)(?=^## \[|\z)/m
38
+ match = changelog.match(version_pattern)
39
+
40
+ if match
41
+ # Clean up the changelog entries
42
+ entries = match[1].strip
43
+ # Remove empty lines at the start and end
44
+ entries.gsub(/\A\s*\n+/, "").gsub(/\n+\s*\z/, "")
45
+ else
46
+ "No changelog entries found for version #{version}"
47
+ end
48
+ end
49
+
50
+ def push_tag_to_remote(version)
51
+ # Check if tag exists on remote
52
+ remote_tag_exists = system("git ls-remote --tags origin | grep -q refs/tags/v#{version}")
53
+
54
+ if remote_tag_exists
55
+ puts "Tag v#{version} already exists on remote. Force pushing updated tag..."
56
+ system("git push origin v#{version} --force")
57
+ else
58
+ puts "Pushing new tag v#{version} to remote..."
59
+ system("git push origin v#{version}")
60
+ end
61
+ end
62
+
34
63
  def bump_version(version_type)
35
64
  unless %w[major minor patch].include?(version_type)
36
65
  puts "Invalid version type. Use: major, minor, or patch"
@@ -87,38 +116,26 @@ def create_git_tag
87
116
  system("git add .")
88
117
  system("git commit -m 'Release #{version}'")
89
118
 
90
- # Check if tag already exists and remove it if it does
119
+ # Check if tag already exists locally and remove it if it does
91
120
  if system("git tag -l v#{version} | grep -q v#{version}")
92
- puts "Tag v#{version} already exists, removing it..."
121
+ puts "Tag v#{version} already exists locally, removing it..."
93
122
  system("git tag -d v#{version}")
94
123
  end
95
124
 
96
- system("git tag v#{version}")
125
+ # Extract changelog entries for this version
126
+ changelog_entries = extract_changelog_for_version(version)
97
127
 
98
- puts "Created git tag v#{version}"
99
- end
100
-
101
- def push_gem_to_rubygems
102
- version = current_version
128
+ # Create tag message with version and changelog
129
+ tag_message = "Release #{version}\n\n#{changelog_entries}"
103
130
 
104
- # Look for gem file in both current directory and pkg directory
105
- gem_file = "amqp-client-#{version}.gem"
106
- pkg_gem_file = "pkg/amqp-client-#{version}.gem"
131
+ # Create annotated tag with the changelog
132
+ system("git", "tag", "-a", "v#{version}", "-m", tag_message)
107
133
 
108
- if File.exist?(pkg_gem_file)
109
- system("gem push #{pkg_gem_file}")
110
- puts "Pushed #{pkg_gem_file} to RubyGems"
111
- elsif File.exist?(gem_file)
112
- system("gem push #{gem_file}")
113
- puts "Pushed #{gem_file} to RubyGems"
114
- else
115
- puts "Gem file #{gem_file} not found in current directory or pkg/. Make sure to build first."
116
- exit 1
117
- end
134
+ puts "Created git tag v#{version} with changelog entries"
118
135
  end
119
136
 
120
- def full_release_process(version_type)
121
- puts "Starting release process..."
137
+ def prepare_release_process(version_type)
138
+ puts "Preparing release process..."
122
139
 
123
140
  # Ensure working directory is clean
124
141
  unless system("git diff --quiet && git diff --cached --quiet")
@@ -138,16 +155,15 @@ def full_release_process(version_type)
138
155
  Rake::Task["release:tag"].invoke
139
156
  Rake::Task["release:tag"].reenable
140
157
 
141
- # Build and push gem
142
- Rake::Task["release:push"].invoke
143
- Rake::Task["release:push"].reenable
144
-
145
158
  # Push to git
146
159
  system("git push origin")
147
- system("git push origin --tags")
148
160
 
161
+ # Handle tag push with potential conflicts
149
162
  version = current_version
150
- puts "Successfully released version #{version}!"
163
+ push_tag_to_remote(version)
164
+
165
+ puts "Successfully prepared release #{version}!"
166
+ puts "The CI will automatically build and publish the gem when the tag is pushed."
151
167
  end
152
168
 
153
169
  namespace :release do
@@ -166,14 +182,15 @@ namespace :release do
166
182
  create_git_tag
167
183
  end
168
184
 
169
- desc "Build and push gem to RubyGems"
170
- task push: :build do
171
- push_gem_to_rubygems
185
+ desc "Push tag to remote (handles conflicts)"
186
+ task :push_tag do
187
+ version = current_version
188
+ push_tag_to_remote(version)
172
189
  end
173
190
 
174
- desc "Full release process (bump version, update changelog, tag, build and push)"
175
- task :full, [:type] => %i[test rubocop] do |_t, args|
176
- full_release_process(args[:type] || "patch")
191
+ desc "Prepare release (bump version, update changelog, create tag, push to git)"
192
+ task :prepare, [:type] => %i[test rubocop] do |_t, args|
193
+ prepare_release_process(args[:type] || "patch")
177
194
  end
178
195
  end
179
196
 
@@ -4,6 +4,8 @@ module AMQP
4
4
  class Client
5
5
  # High level representation of an exchange
6
6
  class Exchange
7
+ attr_reader :name
8
+
7
9
  # Should only be initialized from the Client
8
10
  # @api private
9
11
  def initialize(client, name)
@@ -14,7 +16,7 @@ module AMQP
14
16
  # Publish to the exchange
15
17
  # @param body [String] The message body
16
18
  # @param routing_key [String] The routing key of the message,
17
- # the exchange may use this when routing the message to bound queues
19
+ # the exchange may use this when routing the message to bound queues (defaults to empty string)
18
20
  # @param properties [Properties]
19
21
  # @option properties [String] content_type Content type of the message body
20
22
  # @option properties [String] content_encoding Content encoding of the body
@@ -30,28 +32,30 @@ module AMQP
30
32
  # @option properties [String] user_id Can be used to verify that this is the user that published the message
31
33
  # @option properties [String] app_id Can be used to indicates which app that generated the message
32
34
  # @return [Exchange] self
33
- def publish(body, routing_key, **properties)
35
+ def publish(body, routing_key = "", **properties)
34
36
  @client.publish(body, @name, routing_key, **properties)
35
37
  self
36
38
  end
37
39
 
38
40
  # Bind to another exchange
39
- # @param exchange [String] Name of the exchange to bind to
40
- # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
41
+ # @param source [String, Exchange] Name of the exchange to bind to, or the exchange object itself
42
+ # @param binding_key [String] Binding key on which messages that match might be routed (defaults to empty string)
41
43
  # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
42
44
  # @return [Exchange] self
43
- def bind(exchange, binding_key, arguments: {})
44
- @client.exchange_bind(@name, exchange, binding_key, arguments: arguments)
45
+ def bind(source, binding_key = "", arguments: {})
46
+ source = source.is_a?(String) ? source : source.name
47
+ @client.exchange_bind(@name, source, binding_key, arguments: arguments)
45
48
  self
46
49
  end
47
50
 
48
51
  # Unbind from another exchange
49
- # @param exchange [String] Name of the exchange to unbind from
50
- # @param binding_key [String] Binding key which the queue is bound to the exchange with
52
+ # @param source [String, Exchange] Name of the exchange to unbind from, or the exchange object itself
53
+ # @param binding_key [String] Binding key which the queue is bound to the exchange with (defaults to empty string)
51
54
  # @param arguments [Hash] Arguments matching the binding that's being removed
52
55
  # @return [Exchange] self
53
- def unbind(exchange, binding_key, arguments: {})
54
- @client.exchange_unbind(@name, exchange, binding_key, arguments: arguments)
56
+ def unbind(source, binding_key = "", arguments: {})
57
+ source = source.is_a?(String) ? source : source.name
58
+ @client.exchange_unbind(@name, source, binding_key, arguments: arguments)
55
59
  self
56
60
  end
57
61
 
@@ -4,6 +4,8 @@ module AMQP
4
4
  class Client
5
5
  # Queue abstraction
6
6
  class Queue
7
+ attr_reader :name
8
+
7
9
  # Should only be initialized from the Client
8
10
  # @api private
9
11
  def initialize(client, name)
@@ -35,21 +37,23 @@ module AMQP
35
37
  end
36
38
 
37
39
  # Bind the queue to an exchange
38
- # @param exchange [String] Name of the exchange to bind to
40
+ # @param exchange [String, Exchange] Name of the exchange to bind to, or the exchange object itself
39
41
  # @param binding_key [String] Binding key on which messages that match might be routed (depending on exchange type)
40
42
  # @param arguments [Hash] Message headers to match on (only relevant for header exchanges)
41
43
  # @return [self]
42
44
  def bind(exchange, binding_key, arguments: {})
45
+ exchange = exchange.is_a?(String) ? exchange : exchange.name
43
46
  @client.bind(@name, exchange, binding_key, arguments: arguments)
44
47
  self
45
48
  end
46
49
 
47
50
  # Unbind the queue from an exchange
48
- # @param exchange [String] Name of the exchange to unbind from
51
+ # @param exchange [String, Exchange] Name of the exchange to unbind from, or the exchange object itself
49
52
  # @param binding_key [String] Binding key which the queue is bound to the exchange with
50
53
  # @param arguments [Hash] Arguments matching the binding that's being removed
51
54
  # @return [self]
52
55
  def unbind(exchange, binding_key, arguments: {})
56
+ exchange = exchange.is_a?(String) ? exchange : exchange.name
53
57
  @client.unbind(@name, exchange, binding_key, arguments: arguments)
54
58
  self
55
59
  end
@@ -3,6 +3,6 @@
3
3
  module AMQP
4
4
  class Client
5
5
  # Version of the client library
6
- VERSION = "1.2.0"
6
+ VERSION = "1.2.1"
7
7
  end
8
8
  end
data/lib/amqp/client.rb CHANGED
@@ -83,6 +83,8 @@ module AMQP
83
83
  return if @stopped
84
84
 
85
85
  @stopped = true
86
+ return unless @connq.size.positive?
87
+
86
88
  conn = @connq.pop
87
89
  conn.close
88
90
  nil
@@ -115,6 +117,12 @@ module AMQP
115
117
  end
116
118
 
117
119
  # Declare an exchange and return a high level Exchange object
120
+ # @param name [String] Name of the exchange
121
+ # @param type [String] Type of the exchange, one of "direct", "fanout", "topic", "headers" or custom exchange type
122
+ # @param durable [Boolean] If true the exchange will survive broker restarts
123
+ # @param auto_delete [Boolean] If true the exchange will be deleted when the last queue is unbound
124
+ # @param internal [Boolean] If true the exchange will not accept directly published messages
125
+ # @param arguments [Hash] Custom arguments such as alternate-exchange etc.
118
126
  # @return [Exchange]
119
127
  # @example
120
128
  # amqp = AMQP::Client.new.start
@@ -130,6 +138,42 @@ module AMQP
130
138
  end
131
139
  end
132
140
 
141
+ # Declare a fanout exchange and return a high level Exchange object
142
+ # @param name [String] Name of the exchange (defaults to "amq.fanout")
143
+ # @see {#exchange} for other parameters
144
+ # @return [Exchange]
145
+ def fanout(name = "amq.fanout", **kwargs)
146
+ exchange(name, "fanout", **kwargs)
147
+ end
148
+
149
+ # Declare a direct exchange and return a high level Exchange object
150
+ # @param name [String] Name of the exchange (defaults to "" for the default direct exchange)
151
+ # @see {#exchange} for other parameters
152
+ # @return [Exchange]
153
+ def direct(name = "", **kwargs)
154
+ return exchange(name, "direct", **kwargs) unless name.empty?
155
+
156
+ @exchanges.fetch(name) do
157
+ @exchanges[name] = Exchange.new(self, name)
158
+ end
159
+ end
160
+
161
+ # Declare a topic exchange and return a high level Exchange object
162
+ # @param name [String] Name of the exchange (defaults to "amq.topic")
163
+ # @see {#exchange} for other parameters
164
+ # @return [Exchange]
165
+ def topic(name = "amq.topic", **kwargs)
166
+ exchange(name, "topic", **kwargs)
167
+ end
168
+
169
+ # Declare a headers exchange and return a high level Exchange object
170
+ # @param name [String] Name of the exchange (defaults to "amq.headers")
171
+ # @see {#exchange} for other parameters
172
+ # @return [Exchange]
173
+ def headers(name = "amq.headers", **kwargs)
174
+ exchange(name, "headers", **kwargs)
175
+ end
176
+
133
177
  # @!endgroup
134
178
  # @!group Publish
135
179
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amqp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - CloudAMQP