aws_sqs_extended_client 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8b2bbebadb3a245517db4d6f6e898ac88e59fb3fab3ae565352a966dce65394f
4
+ data.tar.gz: fc70a193700aff4e6749aeedc04cc5941d5d8f9c27824a34512321edc5f9813d
5
+ SHA512:
6
+ metadata.gz: 55544c1f6dd1469429c922e5a5659621fea80eb5340582014bac635d6efb930f35a51f004f7701973c10a7d0083513d896190fb304d53caff758f7a0dc6ad6a0
7
+ data.tar.gz: 8470b1aa27602466c76fedbd184a5461c1dbc662a1c744e1d488840082bfe3143e66ff46395adf339832e0b31a80008712b07cc19e819148f47e17e07e249645
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Hemaraj G
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # sqs_extended_client
2
+
3
+ A Ruby gem that provides support for sending and receiving large messages using Amazon SQS by offloading payloads to Amazon S3, similar to the AWS Java SDK's SQS Extended Client Library.
4
+
5
+ ---
6
+
7
+ ## ✨ Features
8
+
9
+ - ✅ Automatically offloads large messages to S3
10
+ - ✅ Configurable max message size threshold
11
+ - ✅ Optional compression support
12
+ - ✅ Automatic S3 payload cleanup after reading (configurable)
13
+ - ✅ JSON schema validation (planned)
14
+ - ✅ Error handling for missing or inaccessible buckets
15
+ - ✅ Ready for use as a drop-in extension of `Aws::SQS::Client`
16
+
17
+ ---
18
+
19
+ ## 🔧 Installation
20
+
21
+ ```bash
22
+ gem install sqs_extended_client
23
+
24
+ gem 'sqs_extended_client', path: 'path/to/your/local/gem'
25
+
26
+ ```
27
+
28
+ Example:
29
+
30
+
31
+ ```bash
32
+ require 'aws-sdk-sqs'
33
+ require 'aws-sdk-s3'
34
+ require 'sqs_extended_client'
35
+
36
+ sqs = Aws::SQS::Client.new(region: 'us-east-1')
37
+ s3 = Aws::S3::Client.new(region: 'us-east-1')
38
+
39
+ client = SqsExtendedClient::Client.new(
40
+ sqs: sqs,
41
+ s3: s3,
42
+ queue_url: 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
43
+ bucket_name: 'my-sqs-large-message-bucket',
44
+ always_use_s3: false,
45
+ delete_s3_payload_after_read: true,
46
+ compress: true
47
+ )
48
+
49
+ client.send_message("Hello world!")
50
+ messages = client.receive_messages
51
+ ```
@@ -0,0 +1,157 @@
1
+
2
+ require 'aws-sdk-s3'
3
+ require 'aws-sdk-sqs'
4
+ require 'securerandom'
5
+ require 'json'
6
+ require 'zlib'
7
+ require 'logger'
8
+ require_relative 'errors'
9
+ require_relative 'validator'
10
+ require_relative 'constants'
11
+
12
+ module SqsExtendedClient
13
+ include Constant
14
+ class Client
15
+
16
+ def initialize(args = {})
17
+
18
+ Validator.validate_client_options!({
19
+ sqs: args[:sqs], queue_url: args[:queue_url]
20
+ })
21
+
22
+ @sqs = args[:sqs]
23
+ @s3 = args[:s3]
24
+ @queue_url = args[:queue_url]
25
+ @bucket_name = args[:bucket_name]
26
+ @always_use_s3 = args.key?(:always_use_s3) ? args[:always_use_s3] : false
27
+ @max_message_size = args[:max_message_size] || Constant::DEFAULT_MAX_SQS_MESSAGE_SIZE
28
+ @delete_s3_payload_after_read = args.key?(:delete_s3_payload_after_read) ? args[:delete_s3_payload_after_read] : false
29
+ @compress = args.key?(:compress) ? args[:compress] : false
30
+ @logger = args[:logger] || Logger.new($stdout)
31
+ end
32
+
33
+ def send_message(message_body = nil)
34
+ store_method = Validator.validate_send_message!({
35
+ message_body: message_body,
36
+ always_use_s3: @always_use_s3,
37
+ max_message_size: @max_message_size,
38
+ bucket_name: @bucket_name,
39
+ s3: @s3
40
+ })
41
+
42
+ if store_method == :use_s3
43
+ key = "sqs-large/#{SecureRandom.uuid}.json"
44
+ data = @compress ? Zlib::Deflate.deflate(message_body) : message_body
45
+
46
+ begin
47
+ @s3.put_object(bucket: @bucket_name, key: key, body: data)
48
+ @logger&.info("Uploaded large message to S3: #{key}")
49
+ rescue Aws::S3::Errors::NoSuchBucket => e
50
+ raise BucketNotFoundError.new(@bucket_name, cause: e)
51
+ rescue Aws::S3::Errors::AccessDenied => e
52
+ raise AccessDeniedError.new("Access denied to bucket: #{@bucket_name}", cause: e)
53
+ rescue Aws::S3::Errors::ServiceError => e
54
+ raise S3UploadError.new("Failed to upload to S3: #{e.message}", cause: e)
55
+ rescue StandardError => e
56
+ @logger&.error("Unexpected S3 error while uploading: #{e.class} - #{e.message}")
57
+ raise S3UploadError.new("Unexpected error during S3 upload", cause: e)
58
+ end
59
+
60
+ body = {
61
+ s3_pointer: { bucket: @bucket_name, key: key },
62
+ compressed: @compress
63
+ }.to_json
64
+ else
65
+ body = message_body
66
+ end
67
+
68
+ begin
69
+ @sqs.send_message(queue_url: @queue_url, message_body: body)
70
+ @logger&.info("Sent message to SQS#{' (S3 pointer)' if store_method == :use_s3}")
71
+ rescue Aws::SQS::Errors::ServiceError => e
72
+ @logger&.error("Failed to send message to SQS: #{e.class} - #{e.message}")
73
+ raise SqsSendError.new("SQS send failed: #{e.message}", cause: e)
74
+ rescue StandardError => e
75
+ @logger&.error("Unexpected error during SQS send: #{e.class} - #{e.message}")
76
+ raise SqsSendError.new("Unexpected error during SQS send", cause: e)
77
+ end
78
+ end
79
+
80
+
81
+ def receive_messages(args = {})
82
+ max_messages = args[:max_number] || Constant::MAX_MESSAGES
83
+ wait_time = args[:wait_time_seconds] || Constant::LONG_POLL_WAIT_TIME
84
+ auto_delete = args.key?(:auto_delete) ? args[:auto_delete] : true
85
+
86
+ resp = @sqs.receive_message(
87
+ queue_url: @queue_url,
88
+ max_number_of_messages: max_messages,
89
+ wait_time_seconds: wait_time,
90
+ message_attribute_names: ["All"]
91
+ )
92
+
93
+ return [] if resp.messages.empty?
94
+
95
+ resp.messages.map do |msg|
96
+ begin
97
+ decoded_body = parse_message_body(msg)
98
+ msg.define_singleton_method(:decoded_body) { decoded_body }
99
+
100
+ msg
101
+ rescue JSON::ParserError => e
102
+ @logger&.error("Failed to parse JSON from message #{msg.message_id}: #{e.message}")
103
+ nil
104
+ rescue Aws::S3::Errors::NoSuchKey => e
105
+ @logger&.error("S3 object not found for message #{msg.message_id}: #{e.message}")
106
+ nil
107
+ rescue Aws::S3::Errors::NoSuchBucket => e
108
+ @logger&.error("S3 bucket not found for message #{msg.message_id}: #{e.message}")
109
+ nil
110
+ rescue Aws::S3::Errors::AccessDenied => e
111
+ @logger&.error("Access denied for S3 object in message #{msg.message_id}: #{e.message}")
112
+ nil
113
+ rescue Aws::S3::Errors::ServiceError => e
114
+ @logger&.error("S3 service error while reading message #{msg.message_id}: #{e.message}")
115
+ nil
116
+ rescue Zlib::DataError => e
117
+ @logger&.error("Decompression failed for message #{msg.message_id}: #{e.message}")
118
+ nil
119
+ rescue StandardError => e
120
+ @logger&.error("Unexpected error in message #{msg.message_id}: #{e.class} - #{e.message}")
121
+ nil
122
+ ensure
123
+ if auto_delete
124
+ @sqs.delete_message(queue_url: @queue_url, receipt_handle: msg.receipt_handle)
125
+ @logger&.info("Deleted SQS message: #{msg.message_id}")
126
+ end
127
+ end
128
+ end.compact
129
+ end
130
+
131
+ private
132
+
133
+ def parse_message_body(msg)
134
+ parsed = JSON.parse(msg.body)
135
+
136
+ if parsed && parsed["s3_pointer"]
137
+ bucket = parsed["s3_pointer"]["bucket"]
138
+ key = parsed["s3_pointer"]["key"]
139
+ compressed = parsed["compressed"]
140
+
141
+ s3_data = @s3.get_object(bucket: bucket, key: key).body.read
142
+ result = compressed ? Zlib::Inflate.inflate(s3_data) : s3_data
143
+
144
+ p "result #{result}"
145
+
146
+ if @delete_s3_payload_after_read
147
+ @s3.delete_object(bucket: bucket, key: key)
148
+ @logger&.info("Deleted S3 object after read: #{key}")
149
+ end
150
+
151
+ result
152
+ else
153
+ msg.body
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,7 @@
1
+ module SqsExtendedClient
2
+ module Constant
3
+ DEFAULT_MAX_SQS_MESSAGE_SIZE = 256 * 1024.freeze
4
+ MAX_MESSAGES = 10.freeze
5
+ LONG_POLL_WAIT_TIME = 20.freeze
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ module SqsExtendedClient
2
+ class Error < StandardError
3
+ attr_reader :cause
4
+
5
+ def initialize(msg = nil, cause: nil)
6
+ @cause = cause
7
+ super(msg)
8
+ end
9
+ end
10
+
11
+ class BucketNameMissingError < Error
12
+ def initialize
13
+ super("S3 bucket name must be provided but was nil or empty.")
14
+ end
15
+ end
16
+
17
+ class BucketNotFoundError < Error
18
+ attr_reader :bucket_name
19
+
20
+ def initialize(bucket_name, cause: nil)
21
+ @bucket_name = bucket_name
22
+ msg = "S3 bucket not found or not accessible: #{bucket_name}"
23
+ super(msg, cause: cause)
24
+ end
25
+ end
26
+
27
+ class AccessDeniedError < Error
28
+ def initialize(msg = "Access denied to the S3 resource", cause: nil)
29
+ super(msg, cause: cause)
30
+ end
31
+ end
32
+
33
+ class S3UploadError < Error
34
+ def initialize(msg = "Unexpected error while uploading to S3", cause: nil)
35
+ super(msg, cause: cause)
36
+ end
37
+ end
38
+
39
+ class SqsSendError < Error
40
+ def initialize(msg = "SQS send_message failed", cause: nil)
41
+ super(msg, cause: cause)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+
2
+ require_relative 'errors'
3
+
4
+ module SqsExtendedClient
5
+ class Validator
6
+ REQUIRED_ATTRIBUTES = %i[sqs queue_url].freeze
7
+ S3_MESSASGE_ATTRIBUTES = %i[s3 bucket_name].freeze
8
+ # RECEIVE_MESSAGE_ATTRIBUTES =
9
+
10
+ def self.validate_client_options!(options)
11
+ missing = REQUIRED_ATTRIBUTES.select do |key|
12
+ options[key].nil? || options[key].to_s.strip.empty?
13
+ end
14
+
15
+ unless missing.empty?
16
+ raise ArgumentError, "Missing required attribute(s): #{missing.join(', ')}"
17
+ end
18
+ end
19
+
20
+ def self.validate_send_message!(options)
21
+ raise ArgumentError, "Missing required attribute: message_body" if options[:message_body].nil?
22
+
23
+ raise ArgumentError, "max_message_size must be a positive integer" if options[:max_message_size] && options[:max_message_size].to_i <= 0
24
+
25
+ if options[:always_use_s3] || options[:message_body].bytesize > options[:max_message_size]
26
+ missing = S3_MESSASGE_ATTRIBUTES.select do |key|
27
+ options[key].nil? || options[key].to_s.strip.empty?
28
+ end
29
+
30
+ unless missing.empty?
31
+ raise ArgumentError, "Missing required attribute(s): #{missing.join(', ')}"
32
+ end
33
+ return :use_s3
34
+ else
35
+ return :use_sqs
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,4 @@
1
+
2
+ require_relative "aws_sqs_extended_client/client"
3
+ require_relative "aws_sqs_extended_client/errors"
4
+ require_relative "aws_sqs_extended_client/validator"
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws_sqs_extended_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hemaraj G
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-08-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-sqs
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-s3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Ruby gem that mimics AWS SQS Extended Client with features like S3 offloading,
56
+ compression, cleanup, validation, and logging.
57
+ email:
58
+ - hemarajg7@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - LICENSE.txt
64
+ - README.md
65
+ - lib/aws_sqs_extended_client.rb
66
+ - lib/aws_sqs_extended_client/client.rb
67
+ - lib/aws_sqs_extended_client/constants.rb
68
+ - lib/aws_sqs_extended_client/errors.rb
69
+ - lib/aws_sqs_extended_client/validator.rb
70
+ homepage: https://github.com/hemarajg/ruby_aws_extended_client
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.2.3
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Extended SQS client with S3 offloading and validation.
93
+ test_files: []