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

Refactor for source generation #258

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gems/smithy-client/sig/smithy-client/shapes.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Smithy
module Client
module Shapes
class Shape
def initialize: (?Hash[Symbol, untyped]) -> void
def initialize: (?Hash[Symbol, untyped]) ?{ (self) -> void } -> void

attr_accessor id: String?
attr_accessor traits: Hash[String, untyped]
Expand Down
138 changes: 138 additions & 0 deletions gems/smithy-client/spec/client_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# frozen_string_literal: true

module ClientHelper
class << self
def sample_shapes
{
# 'smithy.ruby.tests#StreamingBlob' => {
# 'type' => 'blob',
# 'traits' => { 'smithy.api#streaming' => {} }
# },
'smithy.ruby.tests#Enum' => {
'type' => 'enum',
'members' => {
'member' => {
'target' => 'smithy.api#Unit',
'traits' => { 'smithy.api#enumValue' => 'value' }
}
}
},
'smithy.ruby.tests#intEnum' => {
'type' => 'intEnum',
'members' => {
'member' => {
'target' => 'smithy.api#Unit',
'traits' => { 'smithy.api#enumValue' => 1 }
}
}
},
'smithy.ruby.tests#List' => {
'type' => 'list',
'member' => { 'target' => 'smithy.api#String' }
},
'smithy.ruby.tests#Map' => {
'type' => 'map',
'key' => { 'target' => 'smithy.api#String' },
'value' => { 'target' => 'smithy.api#String' }
},
'smithy.ruby.tests#Structure' => {
'type' => 'structure',
'members' => {
'bigDecimal' => { 'target' => 'smithy.api#BigDecimal' },
'bigInteger' => { 'target' => 'smithy.api#BigInteger' },
'blob' => { 'target' => 'smithy.api#Blob' },
'boolean' => { 'target' => 'smithy.api#Boolean' },
'byte' => { 'target' => 'smithy.api#Byte' },
# 'document' => { 'target' => 'smithy.api#Document' },
'double' => { 'target' => 'smithy.api#Double' },
'enum' => { 'target' => 'smithy.ruby.tests#Enum' },
'float' => { 'target' => 'smithy.api#Float' },
'integer' => { 'target' => 'smithy.api#Integer' },
'intEnum' => { 'target' => 'smithy.ruby.tests#intEnum' },
'long' => { 'target' => 'smithy.api#Long' },
'short' => { 'target' => 'smithy.api#Short' },
'string' => { 'target' => 'smithy.api#String' },
'timestamp' => { 'target' => 'smithy.api#Timestamp' },
'structure' => { 'target' => 'smithy.ruby.tests#Structure' },
'list' => { 'target' => 'smithy.ruby.tests#List' },
'map' => { 'target' => 'smithy.ruby.tests#Map' }
# 'union' => { 'target' => 'smithy.api#String' }
}
},
'smithy.ruby.tests#Operation' => {
'type' => 'operation',
'input' => { 'target' => 'smithy.ruby.tests#Structure' },
'output' => { 'target' => 'smithy.ruby.tests#Structure' }
},
'smithy.ruby.tests#SampleClient' => {
'type' => 'service',
'operations' => [
{ 'target' => 'smithy.ruby.tests#Operation' }
]
}
}
end

def sample_client(options = {})
model = options[:model] ||= model(options)
module_name = options[:gem_module] || next_sample_module_name
plan = create_plan(module_name, model, options)

source = source_code(plan)
begin
Object.module_eval(source)
rescue StandardError => e
puts source
raise e
end
Object.const_get(module_name).const_get(:Client)
end

def sample_schema(options = {})
sample_service(options).const_get(:Shapes).const_get(:Schema)
end

private

def create_plan(module_name, model, options)
plan_options = {
gem_name: options[:gem_name] || 'sample',
gem_version: options[:gem_version] || '1.0.0',
gem_namespace: module_name
}
Smithy::Plan.new(model, :client, plan_options)
end

def source_code(plan)
code = []
Smithy::Generators::Client.new(plan).lib_files.each do |file_name, src_code|
next if file_name.end_with?('/customizations.rb')
next if file_name == "lib/#{plan.options[:gem_name]}.rb"

code << src_code
end
code.join("\n")
end

def model(options)
{
'smithy' => smithy(options),
'shapes' => shapes(options)
}
end

def smithy(options)
options.delete(:smithy) || '2.0'
end

def shapes(options)
options.delete(:shapes) || sample_shapes
end

def next_sample_module_name
@sample_client_count ||= 0
@sample_client_count += 1
"SampleClient#{@sample_client_count}"
end
end
end
17 changes: 17 additions & 0 deletions gems/smithy-client/spec/client_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

# THIS IS NOT going to be committed

module Smithy
describe Client do
let(:shapes) { ClientHelper.sample_shapes }

let(:client) { ClientHelper.sample_client(shapes: shapes) }

let(:schema) { service.const_get(:Shapes).const_get(:SCHEMA) }

it 'makes a client' do
expect(client.new(endpoint: 'https://foo.com')).to be_a(Smithy::Client::Base)
end
end
end
5 changes: 4 additions & 1 deletion gems/smithy-client/spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# frozen_string_literal: true

require 'smithy'

require 'simplecov'
SimpleCov.start

require 'webmock/rspec'

require 'smithy-client'

require_relative 'client_helper'

class DummySendPlugin < Smithy::Client::Plugin
class Handler < Smithy::Client::Handler
def call(context)
Expand Down
46 changes: 35 additions & 11 deletions gems/smithy/lib/smithy/generators/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def initialize(plan)

# @return [Enumerator<String, String>] The file paths and their contents to generate.
def generate
files = source_files
files = gem_files
files.each do |file, content|
next if file == "lib/#{@gem_name}/customizations.rb" && should_skip_customizations?

Expand All @@ -22,30 +22,54 @@ def generate
files
end

private

# rubocop:disable Metrics/AbcSize
def source_files
def lib_files
Enumerator.new do |e|
e.yield "#{@gem_name}.gemspec", Views::Client::Gemspec.new(@plan).render
e.yield '.rubocop.yml', Views::Client::RubocopYml.new(@plan).render

e.yield "lib/#{@gem_name}.rb", Views::Client::Module.new(@plan).render
e.yield "lib/#{@gem_name}/customizations.rb", Views::Client::Customizations.new.render
e.yield "lib/#{@gem_name}/errors.rb", Views::Client::Errors.new(@plan).render
e.yield "lib/#{@gem_name}/endpoint_parameters.rb", Views::Client::EndpointParameters.new(@plan).render
e.yield "lib/#{@gem_name}/endpoint_provider.rb", Views::Client::EndpointProvider.new(@plan).render
e.yield "lib/#{@gem_name}/plugins/endpoint.rb", Views::Client::EndpointPlugin.new(@plan).render
e.yield "lib/#{@gem_name}/shapes.rb", Views::Client::Shapes.new(@plan).render
code_generated_plugins.each { |plugin| e.yield plugin.path, plugin.source }
e.yield "lib/#{@gem_name}/types.rb", Views::Client::Types.new(@plan).render
e.yield "lib/#{@gem_name}/shapes.rb", Views::Client::Shapes.new(@plan).render
e.yield "lib/#{@gem_name}/client.rb", Views::Client::Client.new(@plan, code_generated_plugins).render
end
end
# rubocop:enable Metrics/AbcSize

private

e.yield "lib/#{@gem_name}/client.rb", Views::Client::Client.new(@plan).render
def gem_files
Enumerator.new do |e|
e.yield "#{@gem_name}.gemspec", Views::Client::Gemspec.new(@plan).render
e.yield '.rubocop.yml', Views::Client::RubocopYml.new(@plan).render

lib_files.each { |file, content| e.yield file, content }
spec_files.each { |file, content| e.yield file, content }
end
end

def spec_files
Enumerator.new do |e|
e.yield 'spec/spec_helper.rb', Views::Client::SpecHelper.new(@plan).render
e.yield "spec/#{@gem_name}/endpoint_provider_spec.rb", Views::Client::EndpointProviderSpec.new(@plan).render
end
end
# rubocop:enable Metrics/AbcSize

def namespace
@plan.options[:gem_namespace] || Util::Namespace.namespace_from_gem_name(@plan.options[:gem_name])
end

def code_generated_plugins
[
Views::Client::Plugin.new(
class_name: "#{namespace}::Plugins::Endpoint",
path: "lib/#{@gem_name}/plugins/endpoint.rb",
source: Views::Client::EndpointPlugin.new(@plan).render
)
]
end

def should_skip_customizations?
Dir["#{destination_root}/**/*"].any? { |f| f.include?('/customizations.rb') }
Expand Down
2 changes: 1 addition & 1 deletion gems/smithy/lib/smithy/generators/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def source_files
e.yield "#{@gem_name}.gemspec", Views::Client::Gemspec.new(@plan).render
e.yield '.rubocop.yml', Views::Client::RubocopYml.new(@plan).render
e.yield "lib/#{@gem_name}.rb", Views::Client::Module.new(@plan).render
e.yield "lib/#{@gem_name}/shapes.rb", Views::Client::Shapes.new(@plan).render
e.yield "lib/#{@gem_name}/types.rb", Views::Client::Types.new(@plan).render
e.yield "lib/#{@gem_name}/shapes.rb", Views::Client::Shapes.new(@plan).render
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions gems/smithy/lib/smithy/views/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ module Client; end
end

# helper classes
require_relative 'client/endpoint_built_in_bindings'
require_relative 'client/endpoint_function_bindings'
require_relative 'client/endpoint_parameter'
require_relative 'client/endpoint_rule_set'
require_relative 'client/endpoint_tests'
require_relative 'client/operation_examples'
require_relative 'client/plugin'
require_relative 'client/plugin_list'
Expand Down
29 changes: 22 additions & 7 deletions gems/smithy/lib/smithy/views/client/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ module Views
module Client
# @api private
class Client < View
def initialize(plan)
def initialize(plan, code_generated_plugins)
@plan = plan
@model = plan.model
@plugins = PluginList.new(plan)
@plugins = plugins(plan, code_generated_plugins)
super()
end

def namespace
Util::Namespace.namespace_from_gem_name(@plan.options[:gem_name])
end

def gem_name
@plan.options[:gem_name]
end
Expand All @@ -25,13 +21,21 @@ def gem_version
end

def require_plugins
@plugins.select(&:requirable?).map(&:require_path)
@plugins.select(&:requirable?).map(&:path)
end

def add_plugins
@plugins.reject(&:default?).map(&:class_name)
end

def plugins(plan, code_generated_plugins)
define_namespaces
code_generated_plugins.each do |plugin|
Object.module_eval(plugin.source)
end
PluginList.new(plan).to_a + code_generated_plugins
end

def docstrings
docstrings = []
docstrings << '@param [Hash] options'
Expand All @@ -48,6 +52,17 @@ def operations
.map { |id, operation| Operation.new(@model, id, operation) }
end

private

def define_namespaces
parent = Object
namespace.split('::') do |mod|
child = mod
parent.const_set(child, ::Module.new) unless parent.const_defined?(child)
parent = parent.const_get(child)
end
end

# @api private
class Operation
def initialize(model, id, operation)
Expand Down
43 changes: 43 additions & 0 deletions gems/smithy/lib/smithy/views/client/endpoint_built_in_bindings.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

module Smithy
module Views
module Client
# @api private
class EndpointBuiltInBindings
DEFAULT_BINDINGS = {
'SDK::Endpoint' => {
render_config: proc do |_plan|
<<~ADD_OPTION
option(
:endpoint,
doc_type: String,
docstring: 'Custom Endpoint'
)
ADD_OPTION
end,
render_build: proc do |_plan|
'config.endpoint'
end,
render_test_set: proc do |_plan, value|
{ 'endpoint' => value }
end
}
}.freeze

def initialize(plan)
@bindings = endpoint_built_in_bindings(plan.welds)
end

attr_reader :bindings

private

def endpoint_built_in_bindings(welds)
welded = welds.map(&:endpoint_built_in_bindings).reduce({}, :merge)
DEFAULT_BINDINGS.merge(welded)
end
end
end
end
end
Loading
Loading