Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instance plugins #3051

Merged
merged 11 commits into from
Jun 20, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
describe 'Interfaces' do

before(:all) do
# TODO : support Aws.config[:sample] = { ... }
@tmpdir = SpecHelper.generate_service(['Sample'], multiple_files: true)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ module {{module_name}}
{{#client_constructor}}
# @overload initialize(options)
# @param [Hash] options
#
# @option options [Array<Seahorse::Client::Plugin>] :plugins ([]])
# A list of plugins to apply to the client. Each plugin is either a
# class name or an instance of a plugin class.
#
{{>documentation}}
{{/client_constructor}}
def initialize(*args)
Expand Down
2 changes: 2 additions & 0 deletions gems/aws-sdk-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Unreleased Changes
------------------

* Feature - Support `:plugins` option on all Clients or using `Aws.config[:plugins]`.

* Issue - Fix trailing slash in endpoint URLs for rest-json and rest-xml services.

3.197.1 (2024-06-19)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ module Plugins
# @api private
class GlobalConfiguration < Seahorse::Client::Plugin

@identifiers = Set.new()
@identifiers = Set.new

# @api private
def before_initialize(client_class, options)
Expand All @@ -55,17 +55,18 @@ def before_initialize(client_class, options)
private

def apply_service_defaults(client_class, options)
if defaults = Aws.config[client_class.identifier]
defaults.each do |option_name, default|
options[option_name] = default unless options.key?(option_name)
end
return unless (defaults = Aws.config[client_class.identifier])

defaults.each do |option_name, default|
options[option_name] = default unless options.key?(option_name)
end
end

def apply_aws_defaults(client_class, options)
def apply_aws_defaults(_client_class, options)
Aws.config.each do |option_name, default|
next if self.class.identifiers.include?(option_name)
next if options.key?(option_name)

options[option_name] = default
end
end
Expand All @@ -80,9 +81,7 @@ def add_identifier(identifier)

# @return [Set<String>]
# @api private
def identifiers
@identifiers
end
attr_reader :identifiers

end
end
Expand Down
1 change: 0 additions & 1 deletion gems/aws-sdk-core/lib/aws-sdk-core/plugins/retry_errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ class RetryErrors < Seahorse::Client::Plugin
functionality of `standard` mode along with automatic client side
throttling. This is a provisional mode that may change behavior
in the future.

DOCS
resolve_retry_mode(cfg)
end
Expand Down
24 changes: 17 additions & 7 deletions gems/aws-sdk-core/lib/seahorse/client/base.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# frozen_string_literal: true

require 'thread'

module Seahorse
module Client
class Base
Expand Down Expand Up @@ -60,6 +58,7 @@ def operation_names
def build_config(plugins, options)
config = Configuration.new
config.add_option(:api)
config.add_option(:plugins)
plugins.each do |plugin|
plugin.add_options(config) if plugin.respond_to?(:add_options)
end
Expand Down Expand Up @@ -96,9 +95,9 @@ def context_for(operation_name, params)
class << self

def new(options = {})
plugins = build_plugins
options = options.dup
before_initialize(plugins, options)
plugins = build_plugins(self.plugins + options.fetch(:plugins, []))
plugins = before_initialize(plugins, options)
client = allocate
client.send(:initialize, plugins, options)
client
Expand Down Expand Up @@ -209,17 +208,28 @@ def define_operation_methods
include(operations_module)
end

def build_plugins
def build_plugins(plugins)
plugins.map { |plugin| plugin.is_a?(Class) ? plugin.new : plugin }
end

def before_initialize(plugins, options)
plugins.each do |plugin|
plugin.before_initialize(self, options) if plugin.respond_to?(:before_initialize)
queue = Queue.new
plugins.each { |plugin| queue.push(plugin) }
until queue.empty?
plugin = queue.pop
next unless plugin.respond_to?(:before_initialize)

plugins_before = options.fetch(:plugins, [])
plugin.before_initialize(self, options)
plugins_after = build_plugins(options.fetch(:plugins, []) - plugins_before)
# Plugins with before_initialize can add other plugins
plugins_after.each { |p| queue.push(p); plugins << p }
end
plugins
end

def inherited(subclass)
super
subclass.instance_variable_set('@plugins', PluginList.new(@plugins))
end

Expand Down
1 change: 0 additions & 1 deletion gems/aws-sdk-core/lib/seahorse/client/plugins/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ class Endpoint < Plugin
'http://example.com'
'https://example.com'
'http://example.com:123'

DOCS

def add_handlers(handlers, config)
Expand Down
54 changes: 52 additions & 2 deletions gems/aws-sdk-core/spec/aws/plugins/global_configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,23 @@ module Plugins
option(:property, 'plugin-default')
end)

let(:plugin) do
p = double('plugin')
allow(p).to receive(:is_a?).with(kind_of(Class)).and_return(false)
p
end

let(:options) do
{
region: 'us-east-1',
credentials: Credentials.new('akid', 'secret'),
plugins: [plugin]
}
end

before(:each) do
Aws.config.clear
Aws.config[:region] = 'us-east-1'
Aws.config[:credentials] = Credentials.new('akid', 'secret')
Aws.config = options
end

before(:all) do
Expand Down Expand Up @@ -45,6 +58,43 @@ module Plugins
expect(GlobalConfigClient.new(property: 'arg').config.property).to eq('arg')
end

context 'plugins' do
it 'instructs plugins to #before_initialize' do
expect(plugin).to receive(:before_initialize).with(GlobalConfigClient, options)
GlobalConfigClient.new
end

it 'instructs plugins to #add_options' do
expect(plugin).to receive(:add_options)
GlobalConfigClient.new
end

it 'instructs plugins to #add_handlers' do
expect(plugin).to receive(:add_handlers).
with(kind_of(Seahorse::Client::HandlerList), kind_of(Struct))
GlobalConfigClient.new
end

it 'instructs plugins to #after_initialize' do
expect(plugin).to receive(:after_initialize).with(kind_of(Seahorse::Client::Base))
GlobalConfigClient.new
end

it 'does not call methods that plugin does not respond to' do
plugin = Object.new
allow(plugin).to receive(:respond_to?).with(:before_initialize).and_return(false)
allow(plugin).to receive(:respond_to?).with(:add_options).and_return(false)
allow(plugin).to receive(:respond_to?).with(:add_handlers).and_return(false)
allow(plugin).to receive(:respond_to?).with(:after_initialize).and_return(false)
expect(plugin).not_to receive(:before_initialize)
expect(plugin).not_to receive(:add_options)
expect(plugin).not_to receive(:add_handlers)
expect(plugin).not_to receive(:after_initialize)
Aws.config[:plugins] = [plugin]
GlobalConfigClient.new
end
end

end
end
end
126 changes: 89 additions & 37 deletions gems/aws-sdk-core/spec/seahorse/client/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ module Client

let(:api) { Seahorse::Model::Api.new }

let(:client_class) { Base.define(api:api) }
let(:client_class) { Base.define(api: api) }

let(:client) { client_class.new(endpoint:'http://example.com') }
let(:client) { client_class.new(endpoint: 'http://example.com') }

let(:plugin_a) { Class.new }

Expand Down Expand Up @@ -143,10 +143,10 @@ module Client

it 'builds and sends a request when it receives a request method' do
expect(client).to receive(:build_request).
with(:operation_name, {foo:'bar'}).
with(:operation_name, { foo: 'bar' }).
and_return(request)
expect(request).to receive(:send_request)
client.operation_name(foo:'bar')
client.operation_name(foo: 'bar')
end

it 'passes block arguments to the request method' do
Expand All @@ -155,7 +155,7 @@ module Client
and_yield('chunk2').
and_yield('chunk3')
chunks = []
client.operation_name(foo:'bar') do |chunk|
client.operation_name(foo: 'bar') do |chunk|
chunks << chunk
end
expect(chunks).to eq(%w(chunk1 chunk2 chunk3))
Expand Down Expand Up @@ -269,59 +269,111 @@ module Client

it 'has a defualt list of plugins' do
client_class = Class.new(Base)
expect(client_class.plugins.to_a).to eq([
expected = [
Plugins::Endpoint,
Plugins::NetHttp,
Plugins::RaiseResponseErrors,
Plugins::ResponseTarget,
Plugins::RequestCallback
])
]
expect(client_class.plugins.to_a).to eq(expected)
end

end

describe '.new' do

let(:plugin) {
let(:plugin) do
p = double('plugin')
allow(p).to receive(:is_a?).with(kind_of(Class)).and_return(false)
p
}

it 'instructs plugins to #before_initialize' do
options = { endpoint: 'http://foo.com' }
expect(plugin).to receive(:before_initialize).with(client_class, options)
client_class.add_plugin(plugin)
client_class.new(options)
end

it 'instructs plugins to #add_options' do
expect(plugin).to receive(:add_options) do |config|
config.add_option(:foo, 'bar')
config.add_option(:endpoint, 'http://foo.com')
config.add_option(:regional_endpoint, false)
context 'class level plugin' do
it 'instructs plugins to #before_initialize' do
options = { endpoint: 'http://foo.com' }
expect(plugin).to receive(:before_initialize).with(client_class, options)
client_class.add_plugin(plugin)
client_class.new(options)
end
client_class.add_plugin(plugin)
expect(client_class.new.config.foo).to eq('bar')
end

it 'instructs plugins to #add_handlers' do
expect(plugin).to receive(:add_handlers).
with(kind_of(HandlerList), kind_of(Struct))
client_class.add_plugin(plugin)
client_class.new(endpoint:'http://foo.com')
end
it 'instructs plugins to #add_options' do
expect(plugin).to receive(:add_options) do |config|
config.add_option(:foo, 'bar')
config.add_option(:endpoint, 'http://foo.com')
config.add_option(:regional_endpoint, false)
end
client_class.add_plugin(plugin)
expect(client_class.new.config.foo).to eq('bar')
end

it 'instructs plugins to #after_initialize' do
expect(plugin).to receive(:after_initialize).with(kind_of(Client::Base))
client_class.add_plugin(plugin)
client_class.new(endpoint:'http://foo.com')
it 'instructs plugins to #add_handlers' do
expect(plugin).to receive(:add_handlers).
with(kind_of(HandlerList), kind_of(Struct))
client_class.add_plugin(plugin)
client_class.new(endpoint: 'http://foo.com')
end

it 'instructs plugins to #after_initialize' do
expect(plugin).to receive(:after_initialize).with(kind_of(Client::Base))
client_class.add_plugin(plugin)
client_class.new(endpoint: 'http://foo.com')
end

it 'does not call methods that plugin does not respond to' do
plugin = Object.new
allow(plugin).to receive(:respond_to?).with(:before_initialize).and_return(false)
allow(plugin).to receive(:respond_to?).with(:add_options).and_return(false)
allow(plugin).to receive(:respond_to?).with(:add_handlers).and_return(false)
allow(plugin).to receive(:respond_to?).with(:after_initialize).and_return(false)
expect(plugin).not_to receive(:before_initialize)
expect(plugin).not_to receive(:add_options)
expect(plugin).not_to receive(:add_handlers)
expect(plugin).not_to receive(:after_initialize)
client_class.add_plugin(plugin)
client_class.new(endpoint: 'http://foo.com')
end
end

it 'does not call methods that plugin does not respond to' do
plugin = Object.new
client_class.add_plugin(plugin)
client_class.new(endpoint:'http://foo.com')
context 'instance level plugin' do
it 'instructs plugins to #before_initialize' do
options = { endpoint: 'http://foo.com', plugins: [plugin] }
expect(plugin).to receive(:before_initialize).with(client_class, options)
client_class.new(options)
end

it 'instructs plugins to #add_options' do
expect(plugin).to receive(:add_options) do |config|
config.add_option(:foo, 'bar')
config.add_option(:endpoint, 'http://foo.com')
config.add_option(:regional_endpoint, false)
end
client_class.new(endpoint: 'http://foo.com', plugins: [plugin])
end

it 'instructs plugins to #add_handlers' do
expect(plugin).to receive(:add_handlers).
with(kind_of(HandlerList), kind_of(Struct))
client_class.new(endpoint: 'http://foo.com', plugins: [plugin])
end

it 'instructs plugins to #after_initialize' do
expect(plugin).to receive(:after_initialize).with(kind_of(Client::Base))
client_class.new(endpoint: 'http://foo.com', plugins: [plugin])
end

it 'does not call methods that plugin does not respond to' do
plugin = Object.new
allow(plugin).to receive(:respond_to?).with(:before_initialize).and_return(false)
allow(plugin).to receive(:respond_to?).with(:add_options).and_return(false)
allow(plugin).to receive(:respond_to?).with(:add_handlers).and_return(false)
allow(plugin).to receive(:respond_to?).with(:after_initialize).and_return(false)
expect(plugin).not_to receive(:before_initialize)
expect(plugin).not_to receive(:add_options)
expect(plugin).not_to receive(:add_handlers)
expect(plugin).not_to receive(:after_initialize)
client_class.new(endpoint: 'http://foo.com', plugins: [plugin])
end
end

end
Expand Down
Loading
Loading