poodle-ruby 1.0.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.
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "configuration"
4
+ require_relative "client"
5
+
6
+ module Poodle
7
+ # Rails integration module for Poodle SDK
8
+ #
9
+ # @example Rails initializer (config/initializers/poodle.rb)
10
+ # Poodle::Rails.configure do |config|
11
+ # config.api_key = Rails.application.credentials.poodle_api_key
12
+ # config.debug = Rails.env.development?
13
+ # end
14
+ #
15
+ # @example Using in Rails controllers
16
+ # class NotificationController < ApplicationController
17
+ # def send_welcome_email
18
+ # response = Poodle::Rails.client.send(
19
+ # from: "welcome@example.com",
20
+ # to: params[:email],
21
+ # subject: "Welcome!",
22
+ # html: render_to_string("welcome_email")
23
+ # )
24
+ #
25
+ # if response.success?
26
+ # render json: { status: "sent" }
27
+ # else
28
+ # render json: { error: response.message }, status: :unprocessable_entity
29
+ # end
30
+ # end
31
+ # end
32
+ module Rails
33
+ class << self
34
+ # @return [Configuration, nil] the global configuration
35
+ attr_reader :configuration
36
+
37
+ # Configure Poodle for Rails applications
38
+ #
39
+ # @yield [Configuration] the configuration object
40
+ # @return [Configuration] the configuration object
41
+ #
42
+ # @example
43
+ # Poodle::Rails.configure do |config|
44
+ # config.api_key = Rails.application.credentials.poodle_api_key
45
+ # config.base_url = "https://api.usepoodle.com"
46
+ # config.timeout = 30
47
+ # config.debug = Rails.env.development?
48
+ # end
49
+ def configure
50
+ @configuration = Configuration.new
51
+ yield(@configuration) if block_given?
52
+ @configuration
53
+ end
54
+
55
+ # Get the global Poodle client for Rails
56
+ #
57
+ # @return [Client] the configured client
58
+ # @raise [RuntimeError] if not configured
59
+ #
60
+ # @example
61
+ # client = Poodle::Rails.client
62
+ # response = client.send(email_params)
63
+ def client
64
+ raise "Poodle not configured. Call Poodle::Rails.configure first." unless @configuration
65
+
66
+ @client ||= Client.new(@configuration)
67
+ end
68
+
69
+ # Reset the configuration and client (useful for testing)
70
+ #
71
+ # @return [void]
72
+ def reset!
73
+ @configuration = nil
74
+ @client = nil
75
+ end
76
+
77
+ # Check if Poodle is configured
78
+ #
79
+ # @return [Boolean] true if configured
80
+ def configured?
81
+ !@configuration.nil?
82
+ end
83
+
84
+ # Get configuration from Rails environment
85
+ #
86
+ # @return [Hash] configuration hash from Rails
87
+ def rails_config
88
+ return {} unless defined?(::Rails)
89
+
90
+ begin
91
+ ::Rails.application.config_for(:poodle)
92
+ rescue StandardError
93
+ {}
94
+ end
95
+ end
96
+
97
+ # Auto-configure from Rails credentials and environment
98
+ #
99
+ # @return [Configuration] the configuration object
100
+ #
101
+ # @example In Rails initializer
102
+ # Poodle::Rails.auto_configure!
103
+ def auto_configure!
104
+ config_hash = rails_config
105
+
106
+ configure do |config|
107
+ configure_api_key(config, config_hash)
108
+ configure_base_url(config, config_hash)
109
+ configure_timeout(config, config_hash)
110
+ configure_debug_mode(config, config_hash)
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ # Configure API key from various sources
117
+ #
118
+ # @param config [Configuration] the configuration object
119
+ # @param config_hash [Hash] configuration from Rails
120
+ def configure_api_key(config, config_hash)
121
+ # Try Rails credentials first, then environment variables
122
+ if defined?(::Rails) && ::Rails.application.credentials.respond_to?(:poodle_api_key)
123
+ config.api_key = ::Rails.application.credentials.poodle_api_key
124
+ end
125
+
126
+ config.api_key ||= config_hash[:api_key] || ENV.fetch("POODLE_API_KEY", nil)
127
+ end
128
+
129
+ # Configure base URL from various sources
130
+ #
131
+ # @param config [Configuration] the configuration object
132
+ # @param config_hash [Hash] configuration from Rails
133
+ def configure_base_url(config, config_hash)
134
+ config.base_url = config_hash[:base_url] || ENV["POODLE_BASE_URL"] || config.base_url
135
+ end
136
+
137
+ # Configure timeout from various sources
138
+ #
139
+ # @param config [Configuration] the configuration object
140
+ # @param config_hash [Hash] configuration from Rails
141
+ def configure_timeout(config, config_hash)
142
+ config.timeout = config_hash[:timeout] || ENV["POODLE_TIMEOUT"]&.to_i || config.timeout
143
+ end
144
+
145
+ # Configure debug mode from various sources
146
+ #
147
+ # @param config [Configuration] the configuration object
148
+ # @param config_hash [Hash] configuration from Rails
149
+ def configure_debug_mode(config, config_hash)
150
+ config.debug = config_hash[:debug] || ENV["POODLE_DEBUG"] == "true" ||
151
+ (defined?(::Rails) && ::Rails.env.development?)
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ # Auto-configure if Rails is detected
158
+ require_relative "rails/railtie" if defined?(Rails)
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "email_response"
4
+
5
+ module Poodle
6
+ # Test utilities and mock classes for testing applications that use Poodle
7
+ #
8
+ # @example RSpec configuration
9
+ # RSpec.configure do |config|
10
+ # config.include Poodle::TestHelpers
11
+ #
12
+ # config.before(:each) do
13
+ # Poodle.test_mode!
14
+ # end
15
+ # end
16
+ #
17
+ # @example Testing email sending
18
+ # it "sends welcome email" do
19
+ # expect {
20
+ # UserMailer.send_welcome(user)
21
+ # }.to change { Poodle.deliveries.count }.by(1)
22
+ #
23
+ # email = Poodle.deliveries.last
24
+ # expect(email[:to]).to eq(user.email)
25
+ # expect(email[:subject]).to include("Welcome")
26
+ # end
27
+ module TestHelpers
28
+ # Mock client that captures emails instead of sending them
29
+ class MockClient
30
+ attr_reader :deliveries, :config
31
+
32
+ def initialize(config = nil)
33
+ @config = config || create_test_config
34
+ @deliveries = []
35
+ end
36
+
37
+ # Mock send method that captures email data
38
+ def send(from:, to:, subject:, html: nil, text: nil)
39
+ delivery = {
40
+ from: from,
41
+ to: to,
42
+ subject: subject,
43
+ html: html,
44
+ text: text,
45
+ sent_at: Time.now
46
+ }
47
+
48
+ @deliveries << delivery
49
+
50
+ EmailResponse.new(
51
+ success: true,
52
+ message: "Email queued for sending (test mode)",
53
+ data: { test_mode: true, delivery_id: @deliveries.length }
54
+ )
55
+ end
56
+
57
+ # Mock send_email method
58
+ def send_email(email)
59
+ if email.is_a?(Hash)
60
+ send(**email.transform_keys(&:to_sym))
61
+ else
62
+ send(
63
+ from: email.from,
64
+ to: email.to,
65
+ subject: email.subject,
66
+ html: email.html,
67
+ text: email.text
68
+ )
69
+ end
70
+ end
71
+
72
+ # Mock send_html method
73
+ def send_html(from:, to:, subject:, html:)
74
+ send(from: from, to: to, subject: subject, html: html)
75
+ end
76
+
77
+ # Mock send_text method
78
+ def send_text(from:, to:, subject:, text:)
79
+ send(from: from, to: to, subject: subject, text: text)
80
+ end
81
+
82
+ # Clear all captured deliveries
83
+ def clear_deliveries
84
+ @deliveries.clear
85
+ end
86
+
87
+ # Get the last delivery
88
+ def last_delivery
89
+ @deliveries.last
90
+ end
91
+
92
+ # Check if any emails were sent to a specific address
93
+ def sent_to?(email_address)
94
+ @deliveries.any? { |delivery| delivery[:to] == email_address }
95
+ end
96
+
97
+ # Get all deliveries sent to a specific address
98
+ def deliveries_to(email_address)
99
+ @deliveries.select { |delivery| delivery[:to] == email_address }
100
+ end
101
+
102
+ # Get all deliveries with a specific subject
103
+ def deliveries_with_subject(subject)
104
+ @deliveries.select { |delivery| delivery[:subject].include?(subject) }
105
+ end
106
+
107
+ # Get SDK version
108
+ def version
109
+ Poodle::VERSION
110
+ end
111
+
112
+ private
113
+
114
+ def create_test_config
115
+ # Create a simple configuration without triggering validation
116
+ config = Configuration.allocate
117
+ config.instance_variable_set(:@api_key, "test_key")
118
+ config.instance_variable_set(:@base_url, "https://api.usepoodle.com")
119
+ config.instance_variable_set(:@timeout, 30)
120
+ config.instance_variable_set(:@connect_timeout, 10)
121
+ config.instance_variable_set(:@debug, false)
122
+ config.instance_variable_set(:@http_options, {})
123
+ config
124
+ end
125
+ end
126
+
127
+ # Test mode configuration
128
+ module TestMode
129
+ class << self
130
+ attr_accessor :enabled
131
+
132
+ def enable!
133
+ @enabled = true
134
+ end
135
+
136
+ def disable!
137
+ @enabled = false
138
+ @mock_client = nil
139
+ end
140
+
141
+ def enabled?
142
+ @enabled == true
143
+ end
144
+
145
+ def mock_client
146
+ @mock_client ||= MockClient.new if enabled?
147
+ end
148
+
149
+ def deliveries
150
+ mock_client&.deliveries || []
151
+ end
152
+
153
+ def clear_deliveries
154
+ mock_client&.clear_deliveries
155
+ end
156
+
157
+ def last_delivery
158
+ mock_client&.last_delivery
159
+ end
160
+ end
161
+ end
162
+
163
+ # Helper methods for test assertions
164
+ module Assertions
165
+ # Assert that an email was sent
166
+ def assert_email_sent(count = 1)
167
+ actual_count = Poodle::TestHelpers::TestMode.deliveries.length
168
+ raise "Expected #{count} email(s) to be sent, but #{actual_count} were sent" unless actual_count == count
169
+ end
170
+
171
+ # Assert that an email was sent to a specific address
172
+ def assert_email_sent_to(email_address)
173
+ deliveries = Poodle::TestHelpers::TestMode.deliveries
174
+ sent = deliveries.any? { |delivery| delivery[:to] == email_address }
175
+ raise "Expected email to be sent to #{email_address}" unless sent
176
+ end
177
+
178
+ # Assert that an email was sent with a specific subject
179
+ def assert_email_sent_with_subject(subject)
180
+ deliveries = Poodle::TestHelpers::TestMode.deliveries
181
+ sent = deliveries.any? { |delivery| delivery[:subject].include?(subject) }
182
+ raise "Expected email to be sent with subject containing '#{subject}'" unless sent
183
+ end
184
+
185
+ # Assert that no emails were sent
186
+ def assert_no_emails_sent
187
+ count = Poodle::TestHelpers::TestMode.deliveries.length
188
+ raise "Expected no emails to be sent, but #{count} were sent" unless count.zero?
189
+ end
190
+ end
191
+
192
+ # Include assertion methods
193
+ include Assertions
194
+
195
+ # Convenience methods for accessing test data
196
+ def poodle_deliveries
197
+ Poodle::TestHelpers::TestMode.deliveries
198
+ end
199
+
200
+ def last_poodle_delivery
201
+ Poodle::TestHelpers::TestMode.last_delivery
202
+ end
203
+
204
+ def clear_poodle_deliveries
205
+ Poodle::TestHelpers::TestMode.clear_deliveries
206
+ end
207
+
208
+ # Create a mock client for testing
209
+ def poodle_mock_client
210
+ Poodle::TestHelpers::MockClient.new
211
+ end
212
+ end
213
+ end
214
+
215
+ # Convenience methods for global access
216
+ module Poodle
217
+ def self.test_mode!
218
+ TestHelpers::TestMode.enable!
219
+ end
220
+
221
+ def self.disable_test_mode!
222
+ TestHelpers::TestMode.disable!
223
+ end
224
+
225
+ def self.test_mode?
226
+ TestHelpers::TestMode.enabled?
227
+ end
228
+
229
+ def self.deliveries
230
+ TestHelpers::TestMode.deliveries
231
+ end
232
+
233
+ def self.clear_deliveries
234
+ TestHelpers::TestMode.clear_deliveries
235
+ end
236
+
237
+ def self.last_delivery
238
+ TestHelpers::TestMode.last_delivery
239
+ end
240
+
241
+ def self.mock_client
242
+ TestHelpers::MockClient.new
243
+ end
244
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Poodle
4
+ # The current version of the Poodle Ruby SDK
5
+ VERSION = "1.0.0"
6
+ end
data/lib/poodle.rb ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "poodle/version"
4
+ require_relative "poodle/configuration"
5
+ require_relative "poodle/email"
6
+ require_relative "poodle/email_response"
7
+ require_relative "poodle/client"
8
+
9
+ # Error classes
10
+ require_relative "poodle/errors/base_error"
11
+ require_relative "poodle/errors/validation_error"
12
+ require_relative "poodle/errors/authentication_error"
13
+ require_relative "poodle/errors/payment_error"
14
+ require_relative "poodle/errors/forbidden_error"
15
+ require_relative "poodle/errors/rate_limit_error"
16
+ require_relative "poodle/errors/network_error"
17
+ require_relative "poodle/errors/server_error"
18
+
19
+ # Additional modules (loaded on demand)
20
+ # require_relative "poodle/test_helpers"
21
+
22
+ # Rails integration (optional)
23
+ begin
24
+ require_relative "poodle/rails" if defined?(Rails)
25
+ rescue LoadError
26
+ # Rails not available, skip Rails integration
27
+ end
28
+
29
+ # Ruby SDK for the Poodle email sending API
30
+ #
31
+ # @example Quick start
32
+ # require "poodle"
33
+ #
34
+ # client = Poodle::Client.new(api_key: "your_api_key")
35
+ # response = client.send(
36
+ # from: "sender@example.com",
37
+ # to: "recipient@example.com",
38
+ # subject: "Hello World",
39
+ # html: "<h1>Hello!</h1>"
40
+ # )
41
+ #
42
+ # puts "Email sent!" if response.success?
43
+ #
44
+ # @example Using environment variables
45
+ # ENV["POODLE_API_KEY"] = "your_api_key"
46
+ # client = Poodle::Client.new
47
+ #
48
+ # @example Error handling
49
+ # begin
50
+ # response = client.send(email_data)
51
+ # rescue Poodle::ValidationError => e
52
+ # puts "Validation failed: #{e.errors}"
53
+ # rescue Poodle::AuthenticationError => e
54
+ # puts "Authentication failed: #{e.message}"
55
+ # rescue Poodle::RateLimitError => e
56
+ # puts "Rate limited. Retry after: #{e.retry_after} seconds"
57
+ # rescue Poodle::Error => e
58
+ # puts "Poodle error: #{e.message}"
59
+ # end
60
+ module Poodle
61
+ # Convenience method to create a new client
62
+ #
63
+ # @param args [Array] arguments to pass to Client.new
64
+ # @param kwargs [Hash] keyword arguments to pass to Client.new
65
+ # @return [Client] a new client instance
66
+ #
67
+ # @example
68
+ # client = Poodle.new(api_key: "your_api_key")
69
+ # # equivalent to: Poodle::Client.new(api_key: "your_api_key")
70
+ def self.new(*args, **kwargs)
71
+ Client.new(*args, **kwargs)
72
+ end
73
+
74
+ # Get the SDK version
75
+ #
76
+ # @return [String] the SDK version
77
+ def self.version
78
+ VERSION
79
+ end
80
+ end
data/sig/poodle.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Poodle
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: poodle-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Wilbert Liu
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-05-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
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: faraday-net_http
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ description: Ruby SDK for the Poodle email sending API
42
+ email:
43
+ - wilbert@usepoodle.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".rspec"
49
+ - ".rubocop.yml"
50
+ - ".yardopts"
51
+ - CODE_OF_CONDUCT.md
52
+ - CONTRIBUTING.md
53
+ - LICENSE
54
+ - README.md
55
+ - Rakefile
56
+ - examples/advanced_usage.rb
57
+ - examples/basic_usage.rb
58
+ - lib/poodle.rb
59
+ - lib/poodle/client.rb
60
+ - lib/poodle/configuration.rb
61
+ - lib/poodle/email.rb
62
+ - lib/poodle/email_response.rb
63
+ - lib/poodle/errors/authentication_error.rb
64
+ - lib/poodle/errors/base_error.rb
65
+ - lib/poodle/errors/forbidden_error.rb
66
+ - lib/poodle/errors/network_error.rb
67
+ - lib/poodle/errors/payment_error.rb
68
+ - lib/poodle/errors/rate_limit_error.rb
69
+ - lib/poodle/errors/server_error.rb
70
+ - lib/poodle/errors/validation_error.rb
71
+ - lib/poodle/http_client.rb
72
+ - lib/poodle/rails.rb
73
+ - lib/poodle/rails/railtie.rb
74
+ - lib/poodle/rails/tasks.rake
75
+ - lib/poodle/test_helpers.rb
76
+ - lib/poodle/version.rb
77
+ - sig/poodle.rbs
78
+ homepage: https://usepoodle.com
79
+ licenses:
80
+ - MIT
81
+ metadata:
82
+ allowed_push_host: https://rubygems.org
83
+ homepage_uri: https://usepoodle.com
84
+ source_code_uri: https://github.com/usepoodle/poodle-ruby
85
+ documentation_uri: https://rubydoc.info/gems/poodle-ruby
86
+ bug_tracker_uri: https://github.com/usepoodle/poodle-ruby/issues
87
+ rubygems_mfa_required: 'true'
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 3.0.0
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubygems_version: 3.5.22
104
+ signing_key:
105
+ specification_version: 4
106
+ summary: Poodle Ruby SDK
107
+ test_files: []